Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38b3df9405 | ||
|
|
d30a915890 | ||
|
|
91dd373f43 | ||
|
|
6d82f5a608 | ||
|
|
f5c79c8807 | ||
|
|
b76ef0b11a | ||
|
|
3b759c724e | ||
|
|
534141e5a7 | ||
|
|
36a4617efe | ||
|
|
b985a46ed6 | ||
|
|
56e413064a | ||
|
|
495b3918c1 | ||
|
|
a0154dd587 | ||
|
|
ca0f498d0d | ||
|
|
06b08708d9 | ||
|
|
26894db181 | ||
|
|
b81fe3de75 | ||
|
|
d6a1fa0b51 | ||
|
|
0a2e65e190 | ||
|
|
e63fba4df1 | ||
|
|
14fcd715c1 | ||
|
|
1b03fb311a | ||
|
|
1ce1622388 | ||
|
|
140c291344 | ||
|
|
b5f8ed786c | ||
|
|
ab86fc16fe | ||
|
|
806dc96f8c | ||
|
|
d2e7e4905a | ||
|
|
8d9b8b1829 | ||
|
|
e8c1069a77 | ||
|
|
4c81f9e5a2 | ||
|
|
10751bf841 | ||
|
|
aa4ff799f3 | ||
|
|
07395830a5 | ||
|
|
e53bb6ba43 | ||
|
|
fb08a61c66 | ||
|
|
949b5f891c | ||
|
|
3e7ee6ab46 | ||
|
|
ff724732f8 | ||
|
|
03751c27be | ||
|
|
9c474fc241 | ||
|
|
3e7bffc812 | ||
|
|
0a23502441 | ||
|
|
51730bcba3 | ||
|
|
571b5a9938 | ||
|
|
9ab87a8b9b | ||
|
|
64c3721db4 | ||
|
|
c99aebdf68 | ||
|
|
763714307f | ||
|
|
b0ce7152f7 | ||
|
|
34fcd62c23 | ||
|
|
c8f8132db7 | ||
|
|
e44662413a | ||
|
|
a70113a99d | ||
|
|
0b831b5a23 | ||
|
|
77bc2601f1 | ||
|
|
0055be366d | ||
|
|
2f85636a2a | ||
|
|
d3695a3dc3 | ||
|
|
7c71d5165e | ||
|
|
e9373dd8c9 | ||
|
|
313286f70d | ||
|
|
5a4a5e4d98 | ||
|
|
ee2df92a15 | ||
|
|
38120fba13 | ||
|
|
c9ae56a5ec |
No files matched your search
@@ -106,11 +106,16 @@ There is only one agents file - this one. Don't search for another one.
|
|||||||
|
|
||||||
Always use indentation with four space characters in source code files, even if a file currently uses tabs or appears to rely on eight-space indentation. Increase indentation level by exactly one indentation unit (four spaces) per nested block. When parameters or long expressions wrap, indent the continuation with two indentation units for clarity.
|
Always use indentation with four space characters in source code files, even if a file currently uses tabs or appears to rely on eight-space indentation. Increase indentation level by exactly one indentation unit (four spaces) per nested block. When parameters or long expressions wrap, indent the continuation with two indentation units for clarity.
|
||||||
Explicitly prefer reusing existing variables once their previous values are no longer required instead of declaring new locals of the same type. Call out the variable reuse in comments when its meaning changes so the intent stays clear and register pressure remains minimal.
|
Explicitly prefer reusing existing variables once their previous values are no longer required instead of declaring new locals of the same type. Call out the variable reuse in comments when its meaning changes so the intent stays clear and register pressure remains minimal.
|
||||||
|
Do not introduce try/finally blocks solely to release pooled resources. Restructure the code so that every return path releases the resources without relying on finally unless the control flow would become unclear or unmaintainable.
|
||||||
|
|
||||||
Keep the divisor cycle cache limited to a single block in memory (only the on-disk snapshot). When a lookup misses, compute the required cycle on the configured device and discard the transient result instead of scheduling background generation or caching additional blocks.
|
Keep the divisor cycle cache limited to a single block in memory (only the on-disk snapshot). When a lookup misses, compute the required cycle on the configured device and discard the transient result instead of scheduling background generation or caching additional blocks.
|
||||||
|
|
||||||
All code, comments, commit messages, branch names and PR descriptions must be written in American English.
|
All code, comments, commit messages, branch names and PR descriptions must be written in American English.
|
||||||
|
|
||||||
|
- When modifying existing files, preserve all relevant TODO notes and explanatory comments unless they are no longer valid.
|
||||||
|
- Prefer defining data holders as structs or classes with public fields over auto-properties when performance on the hot path is a concern.
|
||||||
|
- Do not add or keep methods that simply wrap another method call without adding any logic; inline those calls or extend the wrapper with meaningful work.
|
||||||
|
|
||||||
## Performance implementation checklist
|
## Performance implementation checklist
|
||||||
|
|
||||||
- Prefer dedicated kernels over flag-controlled or parameterized kernels. Avoid conditional branches in kernels and tight loops by creating separate kernels when necessary.
|
- Prefer dedicated kernels over flag-controlled or parameterized kernels. Avoid conditional branches in kernels and tight loops by creating separate kernels when necessary.
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
@@ -50,7 +49,7 @@ public class GpuPow2ModBenchmarks
|
|||||||
return Pow2ModSingleBit(Input.Exponent, Input.Modulus);
|
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)
|
if (modulus.IsZero || modulus == GpuUInt128.One)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using PerfectNumbers.Core;
|
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Benchmarks;
|
namespace EvenPerfectBitScanner.Benchmarks;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Gpu;
|
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Benchmarks;
|
namespace EvenPerfectBitScanner.Benchmarks;
|
||||||
|
|
||||||
@@ -52,14 +50,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.
|
/// Baseline right-to-left Montgomery ladder; measured 36.36 μs on the small sample set and 117.32 μs on the large one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark(Baseline = true)]
|
[Benchmark(Baseline = true)]
|
||||||
public ulong Baseline()
|
public ulong BaselineCpu()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out _);
|
GetData(out var exponents, out var divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
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;
|
return checksum;
|
||||||
@@ -69,14 +84,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).
|
/// 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>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong BaselineWithKnownCycle()
|
public ulong BaselineWithKnownCycleCpu()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out var cycles);
|
GetData(out var exponents, out var divisors, out var cycles);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
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;
|
return checksum;
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System;
|
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Threading;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
@@ -89,14 +87,32 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
/// large set.
|
/// large set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// [Benchmark(Baseline = true)]
|
// [Benchmark(Baseline = true)]
|
||||||
public ulong MontgomeryWithoutCycle()
|
public ulong MontgomeryWithoutCycleCpu()
|
||||||
{
|
{
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
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 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].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
@@ -107,14 +123,32 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
/// on the large one (0.08×).
|
/// on the large one (0.08×).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong MontgomeryWithPrecomputedCycle()
|
public ulong MontgomeryWithPrecomputedCycleCpu()
|
||||||
{
|
{
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
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>
|
||||||
|
/// 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;
|
return checksum;
|
||||||
@@ -125,7 +159,7 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
/// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline).
|
/// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// [Benchmark]
|
// [Benchmark]
|
||||||
public ulong MontgomeryWithGpuCycleComputation()
|
public ulong MontgomeryWithGpuCycleComputationCpu()
|
||||||
{
|
{
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
@@ -133,15 +167,33 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
|
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycle, divisors[i]);
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 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]
|
[Benchmark]
|
||||||
public ulong MontgomeryWithHeuristicCycleComputation()
|
public ulong MontgomeryWithHeuristicCycleComputationCpu()
|
||||||
{
|
{
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
@@ -149,7 +201,22 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
|
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycle, divisors[i]);
|
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;
|
return checksum;
|
||||||
@@ -279,16 +346,17 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
Console.WriteLine("Trying heuristic. Prime order calculation");
|
Console.WriteLine("Trying heuristic. Prime order calculation");
|
||||||
#endif
|
#endif
|
||||||
PrimeOrderCalculator.PrimeOrderResult orderResult = PrimeOrderCalculator.Calculate(
|
ulong order = PrimeOrderCalculator.Calculate(
|
||||||
modulus,
|
modulus,
|
||||||
_previousPrimeOrder,
|
_previousPrimeOrder,
|
||||||
divisorData,
|
divisorData,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
if (orderResult.Order != 0UL)
|
if (order != 0UL)
|
||||||
{
|
{
|
||||||
_previousPrimeOrder = orderResult.Order;
|
_previousPrimeOrder = order;
|
||||||
return orderResult.Order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
_previousPrimeOrder = null;
|
_previousPrimeOrder = null;
|
||||||
@@ -307,15 +375,16 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
Console.WriteLine("Trying heuristic. Wide prime order calculation");
|
Console.WriteLine("Trying heuristic. Wide prime order calculation");
|
||||||
#endif
|
#endif
|
||||||
PrimeOrderCalculator.PrimeOrderResultWide orderResult = PrimeOrderCalculator.Calculate(
|
UInt128 order = PrimeOrderCalculator.Calculate(
|
||||||
modulus,
|
modulus,
|
||||||
_previousWidePrimeOrder,
|
_previousWidePrimeOrder,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
if (orderResult.Order != UInt128.Zero)
|
if (order != UInt128.Zero)
|
||||||
{
|
{
|
||||||
_previousWidePrimeOrder = orderResult.Order;
|
_previousWidePrimeOrder = order;
|
||||||
return orderResult.Order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
_previousWidePrimeOrder = null;
|
_previousWidePrimeOrder = null;
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System;
|
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Numerics;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
@@ -176,7 +174,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
|
|||||||
return (UInt128)result;
|
return (UInt128)result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ShouldUseSingleBit(in GpuUInt128 exponent)
|
private static bool ShouldUseSingleBit(GpuUInt128 exponent)
|
||||||
{
|
{
|
||||||
if (exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold)
|
if (exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold)
|
||||||
{
|
{
|
||||||
@@ -257,7 +255,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsBitSet(in GpuUInt128 value, int bitIndex)
|
private static bool IsBitSet(GpuUInt128 value, int bitIndex)
|
||||||
{
|
{
|
||||||
if (bitIndex >= 64)
|
if (bitIndex >= 64)
|
||||||
{
|
{
|
||||||
@@ -267,7 +265,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
|
|||||||
return ((value.Low >> bitIndex) & 1UL) != 0UL;
|
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)
|
if (windowStart != 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using BenchmarkDotNet.Configs;
|
|
||||||
using BenchmarkDotNet.Running;
|
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
namespace EvenPerfectBitScanner.Benchmarks;
|
namespace EvenPerfectBitScanner.Benchmarks;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Buffers;
|
using System.Diagnostics;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -45,6 +44,11 @@ internal static class Program
|
|||||||
private static readonly Optimized PrimeIterator = new();
|
private static readonly Optimized PrimeIterator = new();
|
||||||
private static ulong _byDivisorStartPrime;
|
private static ulong _byDivisorStartPrime;
|
||||||
|
|
||||||
|
private static bool _testMode;
|
||||||
|
private static int _testTargetPrimeCount;
|
||||||
|
private static int _testProcessedPrimeCount;
|
||||||
|
private const string TestTimeFileName = "even_perfect_test_time.txt";
|
||||||
|
|
||||||
[ThreadStatic]
|
[ThreadStatic]
|
||||||
private static bool _lastCompositeP;
|
private static bool _lastCompositeP;
|
||||||
|
|
||||||
@@ -81,6 +85,8 @@ internal static class Program
|
|||||||
int scanBatchSize = 2_097_152, sliceSize = 32;
|
int scanBatchSize = 2_097_152, sliceSize = 32;
|
||||||
ulong residueKMax = 5_000_000UL;
|
ulong residueKMax = 5_000_000UL;
|
||||||
string filterFile = string.Empty;
|
string filterFile = string.Empty;
|
||||||
|
UInt128 maxPrimeLimit = UInt128.MaxValue;
|
||||||
|
bool maxPrimeConfigured = false;
|
||||||
string cyclesPath = DefaultCyclesPath;
|
string cyclesPath = DefaultCyclesPath;
|
||||||
int cyclesBatchSize = 512;
|
int cyclesBatchSize = 512;
|
||||||
bool continueCyclesGeneration = false;
|
bool continueCyclesGeneration = false;
|
||||||
@@ -93,6 +99,10 @@ internal static class Program
|
|||||||
ulong remainder = 0UL;
|
ulong remainder = 0UL;
|
||||||
bool startPrimeProvided = false;
|
bool startPrimeProvided = false;
|
||||||
|
|
||||||
|
_testMode = false;
|
||||||
|
_testTargetPrimeCount = 0;
|
||||||
|
_testProcessedPrimeCount = 0;
|
||||||
|
|
||||||
// NTT backend selection (GPU): reference vs staged
|
// NTT backend selection (GPU): reference vs staged
|
||||||
for (; argIndex < args.Length; argIndex++)
|
for (; argIndex < args.Length; argIndex++)
|
||||||
{
|
{
|
||||||
@@ -109,6 +119,20 @@ internal static class Program
|
|||||||
currentP = ulong.Parse(arg.AsSpan(arg.IndexOf('=') + 1));
|
currentP = ulong.Parse(arg.AsSpan(arg.IndexOf('=') + 1));
|
||||||
startPrimeProvided = true;
|
startPrimeProvided = true;
|
||||||
}
|
}
|
||||||
|
else if (arg.StartsWith("--max-prime=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> value = arg.AsSpan(arg.IndexOf('=') + 1);
|
||||||
|
if (UInt128.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out UInt128 parsedMaxPrime))
|
||||||
|
{
|
||||||
|
maxPrimeLimit = parsedMaxPrime;
|
||||||
|
maxPrimeConfigured = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("Invalid value for --max-prime.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (arg.Equals("--increment=bit", StringComparison.OrdinalIgnoreCase))
|
else if (arg.Equals("--increment=bit", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
useBitTransform = true;
|
useBitTransform = true;
|
||||||
@@ -185,6 +209,10 @@ internal static class Program
|
|||||||
{
|
{
|
||||||
useModuloWorkaround = true;
|
useModuloWorkaround = true;
|
||||||
}
|
}
|
||||||
|
else if (arg.Equals("--test", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
_testMode = true;
|
||||||
|
}
|
||||||
else if (arg.Equals("--use-order", StringComparison.OrdinalIgnoreCase))
|
else if (arg.Equals("--use-order", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
useOrder = true;
|
useOrder = true;
|
||||||
@@ -377,6 +405,19 @@ internal static class Program
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int testPrimeCandidateLimit = 0;
|
||||||
|
|
||||||
|
if (_testMode)
|
||||||
|
{
|
||||||
|
currentP = 31UL;
|
||||||
|
remainder = currentP % 6UL;
|
||||||
|
filterFile = string.Empty;
|
||||||
|
maxPrimeLimit = UInt128.MaxValue;
|
||||||
|
maxPrimeConfigured = false;
|
||||||
|
startPrimeProvided = true;
|
||||||
|
testPrimeCandidateLimit = Math.Max(1, threadCount) * 3;
|
||||||
|
}
|
||||||
|
|
||||||
if (useByDivisor)
|
if (useByDivisor)
|
||||||
{
|
{
|
||||||
_byDivisorStartPrime = startPrimeProvided ? currentP : 0UL;
|
_byDivisorStartPrime = startPrimeProvided ? currentP : 0UL;
|
||||||
@@ -386,7 +427,7 @@ internal static class Program
|
|||||||
_byDivisorStartPrime = 0UL;
|
_byDivisorStartPrime = 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useByDivisor && string.IsNullOrEmpty(filterFile))
|
if (useByDivisor && !_testMode && string.IsNullOrEmpty(filterFile))
|
||||||
{
|
{
|
||||||
Console.WriteLine("--mersenne=bydivisor requires --filter-p=<path>.");
|
Console.WriteLine("--mersenne=bydivisor requires --filter-p=<path>.");
|
||||||
return;
|
return;
|
||||||
@@ -604,30 +645,63 @@ internal static class Program
|
|||||||
File.WriteAllText(ResultsFileName, $"p,searchedMersenne,detailedCheck,passedAllTests{Environment.NewLine}");
|
File.WriteAllText(ResultsFileName, $"p,searchedMersenne,detailedCheck,passedAllTests{Environment.NewLine}");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool useFilter = !string.IsNullOrEmpty(filterFile);
|
bool useFilter = !_testMode && !string.IsNullOrEmpty(filterFile);
|
||||||
HashSet<ulong> filter = [];
|
HashSet<ulong> filter = [];
|
||||||
List<ulong> byDivisorCandidates = [];
|
List<ulong> byDivisorCandidates = [];
|
||||||
ulong maxP = 0UL;
|
ulong maxP = 0UL;
|
||||||
ulong[] localFilter = Array.Empty<ulong>();
|
ulong[] localFilter = Array.Empty<ulong>();
|
||||||
int filterCount = 0;
|
int filterCount = 0;
|
||||||
if (useFilter)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Loading filter...");
|
|
||||||
if (_useByDivisorMode)
|
if (_useByDivisorMode)
|
||||||
{
|
{
|
||||||
byDivisorCandidates = LoadByDivisorCandidates(filterFile);
|
if (_testMode)
|
||||||
|
{
|
||||||
|
byDivisorCandidates = BuildTestPrimeCandidates(testPrimeCandidateLimit);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
Console.WriteLine("Loading filter...");
|
||||||
|
int skippedByLimit = 0;
|
||||||
|
byDivisorCandidates = LoadByDivisorCandidates(filterFile, maxPrimeLimit, maxPrimeConfigured, out skippedByLimit);
|
||||||
|
if (maxPrimeConfigured)
|
||||||
|
{
|
||||||
|
if (skippedByLimit > 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Skipped {skippedByLimit} by-divisor candidate(s) above --max-prime limit.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byDivisorCandidates.Count == 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine("No by-divisor candidates fall within the --max-prime limit.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (useFilter)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Loading filter...");
|
||||||
// TODO: Rent this filter buffer from ArrayPool<ulong> and reuse it across reload batches so
|
// TODO: Rent this filter buffer from ArrayPool<ulong> and reuse it across reload batches so
|
||||||
// we do not allocate fresh arrays while replaying large result filters.
|
// we do not allocate fresh arrays while replaying large result filters.
|
||||||
localFilter = new ulong[1024];
|
localFilter = new ulong[1024];
|
||||||
filterCount = 0;
|
filterCount = 0;
|
||||||
|
bool addedWithinLimit = false;
|
||||||
|
int skippedByLimit = 0;
|
||||||
LoadResultsFile(filterFile, (p, detailedCheck, passedAllTests) =>
|
LoadResultsFile(filterFile, (p, detailedCheck, passedAllTests) =>
|
||||||
{
|
{
|
||||||
if (passedAllTests)
|
if (!passedAllTests)
|
||||||
{
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxPrimeConfigured && (UInt128)p > maxPrimeLimit)
|
||||||
|
{
|
||||||
|
skippedByLimit++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
localFilter[filterCount++] = p;
|
localFilter[filterCount++] = p;
|
||||||
|
addedWithinLimit = true;
|
||||||
if (p > maxP)
|
if (p > maxP)
|
||||||
{
|
{
|
||||||
maxP = p;
|
maxP = p;
|
||||||
@@ -639,14 +713,32 @@ internal static class Program
|
|||||||
filterCount = 0;
|
filterCount = 0;
|
||||||
Console.WriteLine($"Added {p}");
|
Console.WriteLine($"Added {p}");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (filterCount > 0)
|
if (filterCount > 0)
|
||||||
{
|
{
|
||||||
filter.AddRange(localFilter[..filterCount]);
|
filter.AddRange(localFilter[..filterCount]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (maxPrimeConfigured)
|
||||||
|
{
|
||||||
|
if (!addedWithinLimit)
|
||||||
|
{
|
||||||
|
Console.WriteLine("No filter candidates fall within the --max-prime limit.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (skippedByLimit > 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Skipped {skippedByLimit} filter candidate(s) above --max-prime limit.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (_testMode)
|
||||||
|
{
|
||||||
|
_testTargetPrimeCount = testPrimeCandidateLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_primeCount >= 2)
|
if (_primeCount >= 2)
|
||||||
@@ -661,6 +753,12 @@ internal static class Program
|
|||||||
// Configure batch size for GPU primality sieve
|
// Configure batch size for GPU primality sieve
|
||||||
PrimeTester.GpuBatchSize = gpuPrimeBatch;
|
PrimeTester.GpuBatchSize = gpuPrimeBatch;
|
||||||
|
|
||||||
|
Stopwatch? stopwatch = null;
|
||||||
|
if (_testMode)
|
||||||
|
{
|
||||||
|
stopwatch = Stopwatch.StartNew();
|
||||||
|
}
|
||||||
|
|
||||||
Console.WriteLine("Starting scan...");
|
Console.WriteLine("Starting scan...");
|
||||||
|
|
||||||
if (_useByDivisorMode)
|
if (_useByDivisorMode)
|
||||||
@@ -668,6 +766,12 @@ internal static class Program
|
|||||||
RunByDivisorMode(byDivisorCandidates, threadCount);
|
RunByDivisorMode(byDivisorCandidates, threadCount);
|
||||||
FlushBuffer();
|
FlushBuffer();
|
||||||
StringBuilderPool.Return(_outputBuilder!);
|
StringBuilderPool.Return(_outputBuilder!);
|
||||||
|
if (stopwatch is not null)
|
||||||
|
{
|
||||||
|
stopwatch.Stop();
|
||||||
|
ReportTestTime(stopwatch.Elapsed);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -682,7 +786,14 @@ internal static class Program
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_testMode)
|
||||||
|
{
|
||||||
|
testPrimeCandidateLimit = Math.Max(1, threadCount) * 3;
|
||||||
|
_testTargetPrimeCount = testPrimeCandidateLimit;
|
||||||
|
}
|
||||||
|
|
||||||
_state = ((long)currentP << 3) | (long)remainder;
|
_state = ((long)currentP << 3) | (long)remainder;
|
||||||
|
|
||||||
Task[] tasks = new Task[threadCount];
|
Task[] tasks = new Task[threadCount];
|
||||||
|
|
||||||
int taskIndex = 0;
|
int taskIndex = 0;
|
||||||
@@ -752,13 +863,71 @@ internal static class Program
|
|||||||
}
|
}
|
||||||
|
|
||||||
Task.WaitAll(tasks);
|
Task.WaitAll(tasks);
|
||||||
FlushBuffer();
|
|
||||||
StringBuilderPool.Return(_outputBuilder!);
|
TimeSpan testElapsed = default;
|
||||||
|
bool reportTestTime = false;
|
||||||
|
if (stopwatch is not null)
|
||||||
|
{
|
||||||
|
stopwatch.Stop();
|
||||||
|
testElapsed = stopwatch.Elapsed;
|
||||||
|
reportTestTime = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<ulong> LoadByDivisorCandidates(string candidateFile)
|
FlushBuffer();
|
||||||
|
StringBuilderPool.Return(_outputBuilder!);
|
||||||
|
|
||||||
|
if (reportTestTime)
|
||||||
|
{
|
||||||
|
ReportTestTime(testElapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ulong> BuildTestPrimeCandidates(int targetCount)
|
||||||
|
{
|
||||||
|
if (targetCount <= 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ulong> candidates = new(targetCount);
|
||||||
|
ulong previous = 29UL;
|
||||||
|
int remaining = targetCount;
|
||||||
|
ulong nextPrime = 0UL;
|
||||||
|
|
||||||
|
while (remaining > 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
nextPrime = PrimeIterator.Next(in previous);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextPrime < 31UL)
|
||||||
|
{
|
||||||
|
previous = nextPrime;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates.Add(nextPrime);
|
||||||
|
remaining--;
|
||||||
|
previous = nextPrime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remaining > 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Unable to populate the requested number of test primes before reaching the 64-bit limit.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ulong> LoadByDivisorCandidates(string candidateFile, UInt128 maxPrimeLimit, bool maxPrimeConfigured, out int skippedByLimit)
|
||||||
{
|
{
|
||||||
List<ulong> candidates = [];
|
List<ulong> candidates = [];
|
||||||
|
skippedByLimit = 0;
|
||||||
using FileStream readStream = new(candidateFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
using FileStream readStream = new(candidateFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
using StreamReader reader = new(readStream);
|
using StreamReader reader = new(readStream);
|
||||||
string? line;
|
string? line;
|
||||||
@@ -797,9 +966,16 @@ internal static class Program
|
|||||||
// TODO: Swap ulong.TryParse for the Utf8Parser-based fast path once the shared span helpers
|
// TODO: Swap ulong.TryParse for the Utf8Parser-based fast path once the shared span helpers
|
||||||
// arrive so by-divisor candidate loading avoids per-value string allocations.
|
// arrive so by-divisor candidate loading avoids per-value string allocations.
|
||||||
if (ulong.TryParse(span[start..index], NumberStyles.None, CultureInfo.InvariantCulture, out ulong parsed))
|
if (ulong.TryParse(span[start..index], NumberStyles.None, CultureInfo.InvariantCulture, out ulong parsed))
|
||||||
|
{
|
||||||
|
if (!maxPrimeConfigured || (UInt128)parsed <= maxPrimeLimit)
|
||||||
{
|
{
|
||||||
candidates.Add(parsed);
|
candidates.Add(parsed);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
skippedByLimit++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -896,6 +1072,7 @@ internal static class Program
|
|||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
Console.WriteLine("Options:");
|
Console.WriteLine("Options:");
|
||||||
Console.WriteLine(" --prime=<value> starting exponent (p)");
|
Console.WriteLine(" --prime=<value> starting exponent (p)");
|
||||||
|
Console.WriteLine(" --max-prime=<value> inclusive upper bound for primes from filter files");
|
||||||
Console.WriteLine(" --increment=bit|add exponent increment method");
|
Console.WriteLine(" --increment=bit|add exponent increment method");
|
||||||
Console.WriteLine(" --threads=<value> number of worker threads");
|
Console.WriteLine(" --threads=<value> number of worker threads");
|
||||||
Console.WriteLine(" --block-size=<value> values processed per thread batch");
|
Console.WriteLine(" --block-size=<value> values processed per thread batch");
|
||||||
@@ -989,6 +1166,21 @@ internal static class Program
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void OnPrimeCandidateConfirmed()
|
||||||
|
{
|
||||||
|
if (!_testMode)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int processed = Interlocked.Increment(ref _testProcessedPrimeCount);
|
||||||
|
if (processed == _testTargetPrimeCount)
|
||||||
|
{
|
||||||
|
Volatile.Write(ref _limitReached, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static bool TryAcquireConsoleSlot(bool primeCandidate, out bool primeFlag)
|
private static bool TryAcquireConsoleSlot(bool primeCandidate, out bool primeFlag)
|
||||||
{
|
{
|
||||||
@@ -1125,6 +1317,75 @@ internal static class Program
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ReportTestTime(TimeSpan elapsed)
|
||||||
|
{
|
||||||
|
double elapsedSeconds = elapsed.TotalSeconds;
|
||||||
|
double elapsedMilliseconds = elapsed.TotalMilliseconds;
|
||||||
|
Console.WriteLine($"Test elapsed time: {elapsedSeconds:F3} s");
|
||||||
|
|
||||||
|
string filePath = GetTestTimeFilePath();
|
||||||
|
bool hasPrevious = TryReadTestTime(filePath, out double previousMilliseconds);
|
||||||
|
string elapsedText = elapsedMilliseconds.ToString("F3", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
if (!hasPrevious)
|
||||||
|
{
|
||||||
|
WriteTestTime(filePath, elapsedMilliseconds);
|
||||||
|
Console.WriteLine($"FIRST TIME: {elapsedText} ms");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string previousText = previousMilliseconds.ToString("F3", CultureInfo.InvariantCulture);
|
||||||
|
if (elapsedMilliseconds < previousMilliseconds)
|
||||||
|
{
|
||||||
|
WriteTestTime(filePath, elapsedMilliseconds);
|
||||||
|
Console.WriteLine($"BETTER TIME: {elapsedText} ms (previous {previousText} ms)");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"WORSE TIME: {elapsedText} ms (best {previousText} ms)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetTestTimeFilePath()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_resultsDir))
|
||||||
|
{
|
||||||
|
return TestTimeFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Path.Combine(_resultsDir!, TestTimeFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadTestTime(string filePath, out double milliseconds)
|
||||||
|
{
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
{
|
||||||
|
milliseconds = 0.0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string content = File.ReadAllText(filePath).Trim();
|
||||||
|
if (content.Length == 0)
|
||||||
|
{
|
||||||
|
milliseconds = 0.0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return double.TryParse(content, NumberStyles.Float, CultureInfo.InvariantCulture, out milliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteTestTime(string filePath, double milliseconds)
|
||||||
|
{
|
||||||
|
string? directory = Path.GetDirectoryName(filePath);
|
||||||
|
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
string formatted = milliseconds.ToString("F6", CultureInfo.InvariantCulture);
|
||||||
|
File.WriteAllText(filePath, formatted);
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal static ulong CountOnes(ulong value)
|
internal static ulong CountOnes(ulong value)
|
||||||
{
|
{
|
||||||
@@ -1355,6 +1616,8 @@ internal static class Program
|
|||||||
_lastCompositeP = true;
|
_lastCompositeP = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnPrimeCandidateConfirmed();
|
||||||
}
|
}
|
||||||
|
|
||||||
searchedMersenne = true;
|
searchedMersenne = true;
|
||||||
|
|||||||
@@ -15,9 +15,15 @@ public class GpuContextPoolTests
|
|||||||
{
|
{
|
||||||
GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
|
|
||||||
using var lease = GpuContextPool.RentPreferred(preferCpu: true);
|
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||||
|
try
|
||||||
|
{
|
||||||
lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU);
|
lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lease.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
}
|
}
|
||||||
@@ -30,17 +36,27 @@ public class GpuContextPoolTests
|
|||||||
|
|
||||||
Context firstContext;
|
Context firstContext;
|
||||||
Accelerator firstAccelerator;
|
Accelerator firstAccelerator;
|
||||||
using (var lease = GpuContextPool.RentPreferred(preferCpu: true))
|
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
firstContext = lease.Context;
|
firstContext = lease.Context;
|
||||||
firstAccelerator = lease.Accelerator;
|
firstAccelerator = lease.Accelerator;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lease.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
using (var lease = GpuContextPool.RentPreferred(preferCpu: true))
|
lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
lease.Context.Should().BeSameAs(firstContext);
|
lease.Context.Should().BeSameAs(firstContext);
|
||||||
lease.Accelerator.Should().BeSameAs(firstAccelerator);
|
lease.Accelerator.Should().BeSameAs(firstAccelerator);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lease.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
}
|
}
|
||||||
@@ -52,17 +68,27 @@ public class GpuContextPoolTests
|
|||||||
GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
|
|
||||||
Context firstContext;
|
Context firstContext;
|
||||||
using (var lease = GpuContextPool.RentPreferred(preferCpu: true))
|
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
firstContext = lease.Context;
|
firstContext = lease.Context;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lease.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
|
|
||||||
using (var lease = GpuContextPool.RentPreferred(preferCpu: true))
|
lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
lease.Context.Should().NotBeSameAs(firstContext);
|
lease.Context.Should().NotBeSameAs(firstContext);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lease.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using ILGPU;
|
using ILGPU;
|
||||||
using ILGPU.Runtime;
|
using ILGPU.Runtime;
|
||||||
using PerfectNumbers.Core;
|
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@@ -199,11 +196,18 @@ public class GpuKernelCompilationTests
|
|||||||
[MemberData(nameof(KernelLoaders))]
|
[MemberData(nameof(KernelLoaders))]
|
||||||
public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile)
|
public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile)
|
||||||
{
|
{
|
||||||
using var lease = GpuContextPool.RentPreferred(preferCpu: true);
|
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||||
|
try
|
||||||
|
{
|
||||||
var accelerator = lease.Accelerator;
|
var accelerator = lease.Accelerator;
|
||||||
Action action = () => compile(accelerator);
|
Action action = () => compile(accelerator);
|
||||||
action.Should().NotThrow($"kernel {name} should compile");
|
action.Should().NotThrow($"kernel {name} should compile");
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lease.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void CompileDivisorCycleCacheKernel(Accelerator accelerator)
|
private static void CompileDivisorCycleCacheKernel(Accelerator accelerator)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -150,7 +150,9 @@ public class NttGpuMathTests
|
|||||||
var modulus = new GpuUInt128(0UL, 17UL);
|
var modulus = new GpuUInt128(0UL, 17UL);
|
||||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||||
|
|
||||||
using var gpu = GpuContextPool.Rent();
|
GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
|
||||||
|
try
|
||||||
|
{
|
||||||
var accelerator = gpu.Accelerator;
|
var accelerator = gpu.Accelerator;
|
||||||
|
|
||||||
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
|
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
|
||||||
@@ -175,6 +177,11 @@ public class NttGpuMathTests
|
|||||||
prod.Should().Be(r2);
|
prod.Should().Be(r2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
gpu.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
@@ -338,13 +345,24 @@ public class NttGpuMathTests
|
|||||||
values[1] = new GpuUInt128(0UL, 2UL);
|
values[1] = new GpuUInt128(0UL, 2UL);
|
||||||
var modulus = new GpuUInt128(0UL, 17UL);
|
var modulus = new GpuUInt128(0UL, 17UL);
|
||||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||||
using (var gpu = GpuContextPool.Rent())
|
GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var buffer = gpu.Accelerator.Allocate1D<GpuUInt128>(values.Length);
|
var buffer = gpu.Accelerator.Allocate1D<GpuUInt128>(values.Length);
|
||||||
|
try
|
||||||
|
{
|
||||||
buffer.View.CopyFromCPU(ref values[0], values.Length);
|
buffer.View.CopyFromCPU(ref values[0], values.Length);
|
||||||
NttGpuMath.SquareDevice(gpu.Accelerator, buffer.View, modulus, primitiveRoot);
|
NttGpuMath.SquareDevice(gpu.Accelerator, buffer.View, modulus, primitiveRoot);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
buffer.Dispose();
|
buffer.Dispose();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
gpu.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that the square cache now holds entries for the accelerator.
|
// Verify that the square cache now holds entries for the accelerator.
|
||||||
var field = typeof(NttGpuMath).GetField("SquareCache", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var field = typeof(NttGpuMath).GetField("SquareCache", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ public class NttMontgomeryTests
|
|||||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||||
|
|
||||||
// Device execution
|
// Device execution
|
||||||
using var context = GpuContextPool.Rent();
|
GpuContextPool.GpuContextLease context = GpuContextPool.Rent();
|
||||||
|
try
|
||||||
|
{
|
||||||
var accelerator = context.Accelerator;
|
var accelerator = context.Accelerator;
|
||||||
using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
|
using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
|
||||||
buffer.View.CopyFromCPU(ref gpu[0], gpu.Length);
|
buffer.View.CopyFromCPU(ref gpu[0], gpu.Length);
|
||||||
@@ -45,6 +47,11 @@ public class NttMontgomeryTests
|
|||||||
gpu[i].Should().Be(cpu[i]);
|
gpu[i].Should().Be(cpu[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
context.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using System;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core;
|
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@@ -359,10 +357,9 @@ public class PrimeOrderGpuHeuristicsTests
|
|||||||
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||||
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
ulong result = PrimeOrderCalculator.Calculate(prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
result.Should().Be(3UL);
|
||||||
result.Order.Should().Be(3UL);
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ public class GpuKernelPoolTests
|
|||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls()
|
public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls()
|
||||||
{
|
{
|
||||||
using var lease = GpuContextPool.RentPreferred(preferCpu: true);
|
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||||
|
try
|
||||||
|
{
|
||||||
var accelerator = lease.Accelerator;
|
var accelerator = lease.Accelerator;
|
||||||
|
|
||||||
var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||||
@@ -20,6 +22,11 @@ public class GpuKernelPoolTests
|
|||||||
second.Equals(first).Should().BeTrue();
|
second.Equals(first).Should().BeTrue();
|
||||||
second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1);
|
second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lease.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
namespace PerfectNumbers.Core.Tests;
|
||||||
@@ -70,7 +67,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
|||||||
return new DummySession();
|
return new DummySession();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < primes.Length; i++)
|
for (int i = 0; i < primes.Length; i++)
|
||||||
{
|
{
|
||||||
@@ -80,7 +77,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
|||||||
|
|
||||||
private sealed class DummySession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
private sealed class DummySession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||||
{
|
{
|
||||||
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
|
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core.Cpu;
|
using PerfectNumbers.Core.Cpu;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core;
|
|
||||||
using PerfectNumbers.Core.Cpu;
|
using PerfectNumbers.Core.Cpu;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Numerics;
|
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@@ -45,14 +43,14 @@ public class PrimeOrderCalculatorTests
|
|||||||
{
|
{
|
||||||
ulong expected = ComputeOrderByDoubling(prime);
|
ulong expected = ComputeOrderByDoubling(prime);
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
|
ulong result = PrimeOrderCalculator.Calculate(
|
||||||
prime,
|
prime,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
MontgomeryDivisorData.FromModulus(prime),
|
MontgomeryDivisorData.FromModulus(prime),
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
result.Should().Be(expected);
|
||||||
result.Order.Should().Be(expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@@ -62,21 +60,21 @@ public class PrimeOrderCalculatorTests
|
|||||||
{
|
{
|
||||||
ulong previousOrder = ComputeOrderByDoubling(previousPrime);
|
ulong previousOrder = ComputeOrderByDoubling(previousPrime);
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderResult heuristic = PrimeOrderCalculator.Calculate(
|
ulong heuristic = PrimeOrderCalculator.Calculate(
|
||||||
prime,
|
prime,
|
||||||
previousOrder,
|
previousOrder,
|
||||||
MontgomeryDivisorData.FromModulus(prime),
|
MontgomeryDivisorData.FromModulus(prime),
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderResult strict = PrimeOrderCalculator.Calculate(
|
ulong strict = PrimeOrderCalculator.Calculate(
|
||||||
prime,
|
prime,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
MontgomeryDivisorData.FromModulus(prime),
|
MontgomeryDivisorData.FromModulus(prime),
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault);
|
PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
heuristic.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
heuristic.Should().Be(strict);
|
||||||
strict.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
|
||||||
heuristic.Order.Should().Be(strict.Order);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@@ -85,14 +83,14 @@ public class PrimeOrderCalculatorTests
|
|||||||
[InlineData(3UL, 2UL)]
|
[InlineData(3UL, 2UL)]
|
||||||
public void Calculate_handles_trivial_primes(ulong prime, ulong expectedOrder)
|
public void Calculate_handles_trivial_primes(ulong prime, ulong expectedOrder)
|
||||||
{
|
{
|
||||||
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
|
ulong result = PrimeOrderCalculator.Calculate(
|
||||||
prime,
|
prime,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
MontgomeryDivisorData.FromModulus(prime),
|
MontgomeryDivisorData.FromModulus(prime),
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
result.Should().Be(expectedOrder);
|
||||||
result.Order.Should().Be(expectedOrder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -105,14 +103,14 @@ public class PrimeOrderCalculatorTests
|
|||||||
maxPowChecks: 8,
|
maxPowChecks: 8,
|
||||||
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
|
ulong result = PrimeOrderCalculator.Calculate(
|
||||||
prime: 13UL,
|
prime: 13UL,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
MontgomeryDivisorData.FromModulus(13UL),
|
MontgomeryDivisorData.FromModulus(13UL),
|
||||||
config);
|
config,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved);
|
result.Should().Be(ComputeOrderByDoubling(13UL));
|
||||||
result.Order.Should().Be(ComputeOrderByDoubling(13UL));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -126,14 +124,14 @@ public class PrimeOrderCalculatorTests
|
|||||||
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(239UL);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(239UL);
|
||||||
PrimeOrderCalculator.PrimeOrderResult heuristic = PrimeOrderCalculator.Calculate(
|
ulong heuristic = PrimeOrderCalculator.Calculate(
|
||||||
prime: 239UL,
|
prime: 239UL,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
divisorData,
|
divisorData,
|
||||||
heuristicConfig);
|
heuristicConfig,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
heuristic.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved);
|
heuristic.Should().Be(ComputeOrderByDoubling(239UL));
|
||||||
heuristic.Order.Should().Be(ComputeOrderByDoubling(239UL));
|
|
||||||
|
|
||||||
var strictConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig(
|
var strictConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig(
|
||||||
smallFactorLimit: 2,
|
smallFactorLimit: 2,
|
||||||
@@ -141,14 +139,14 @@ public class PrimeOrderCalculatorTests
|
|||||||
maxPowChecks: 1,
|
maxPowChecks: 1,
|
||||||
mode: PrimeOrderCalculator.PrimeOrderMode.Strict);
|
mode: PrimeOrderCalculator.PrimeOrderMode.Strict);
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderResult strict = PrimeOrderCalculator.Calculate(
|
ulong strict = PrimeOrderCalculator.Calculate(
|
||||||
prime: 239UL,
|
prime: 239UL,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
divisorData,
|
divisorData,
|
||||||
strictConfig);
|
strictConfig,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
strict.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
strict.Should().Be(heuristic);
|
||||||
strict.Order.Should().Be(heuristic.Order);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -157,13 +155,13 @@ public class PrimeOrderCalculatorTests
|
|||||||
{
|
{
|
||||||
UInt128 prime = UInt128.Parse("18446744073709641691");
|
UInt128 prime = UInt128.Parse("18446744073709641691");
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderResultWide result = PrimeOrderCalculator.Calculate(
|
UInt128 result = PrimeOrderCalculator.Calculate(
|
||||||
prime,
|
prime,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
result.Should().Be((UInt128)1229782938247309446UL);
|
||||||
result.Order.Should().Be((UInt128)1229782938247309446UL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
namespace PerfectNumbers.Core.Tests;
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
namespace PerfectNumbers.Core.Cpu;
|
||||||
|
|
||||||
|
internal struct FactorCacheLease
|
||||||
|
{
|
||||||
|
private Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? _cache;
|
||||||
|
|
||||||
|
public void EnsureInitialized(ref Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? cache)
|
||||||
|
{
|
||||||
|
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? current = cache;
|
||||||
|
if (current is null)
|
||||||
|
{
|
||||||
|
current = ThreadStaticPools.RentMersenneFactorCacheDictionary();
|
||||||
|
cache = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cache = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? cache = _cache;
|
||||||
|
if (cache is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadStaticPools.ReturnMersenneFactorCacheDictionary(cache);
|
||||||
|
_cache = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
namespace PerfectNumbers.Core.Cpu;
|
||||||
|
|
||||||
|
internal sealed class MersenneCpuDivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||||
|
{
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_disposed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CheckDivisor(
|
||||||
|
ulong divisor,
|
||||||
|
in MontgomeryDivisorData divisorData,
|
||||||
|
ulong divisorCycle,
|
||||||
|
in ReadOnlySpan<ulong> primes,
|
||||||
|
Span<byte> hits)
|
||||||
|
{
|
||||||
|
int length = primes.Length;
|
||||||
|
// EvenPerfectBitScanner always provides at least one exponent on this path.
|
||||||
|
// Keep the guard commented out for tests that might reuse the session differently.
|
||||||
|
// if (length == 0)
|
||||||
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
MontgomeryDivisorData cachedData = divisorData;
|
||||||
|
if (cachedData.Modulus != divisor)
|
||||||
|
{
|
||||||
|
cachedData = MontgomeryDivisorDataCache.Get(divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (divisorCycle == 0UL)
|
||||||
|
{
|
||||||
|
divisorCycle = DivisorCycleCache.Shared.GetCycleLength(divisor);
|
||||||
|
if (divisorCycle == 0UL)
|
||||||
|
{
|
||||||
|
hits.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep these remainder steppers in place so future updates continue reusing the previously computed residues.
|
||||||
|
// They are critical for avoiding repeated full Montgomery exponentiation work when scanning divisors.
|
||||||
|
var exponentStepper = new ExponentRemainderStepper(cachedData);
|
||||||
|
if (!exponentStepper.IsValidModulus)
|
||||||
|
{
|
||||||
|
hits.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cycleStepper = new CycleRemainderStepper(divisorCycle);
|
||||||
|
|
||||||
|
ulong remainder = cycleStepper.Initialize(primes[0]);
|
||||||
|
hits[0] = remainder == 0UL
|
||||||
|
? (exponentStepper.ComputeNextIsUnity(primes[0]) ? (byte)1 : (byte)0)
|
||||||
|
: (byte)0;
|
||||||
|
|
||||||
|
for (int i = 1; i < length; i++)
|
||||||
|
{
|
||||||
|
remainder = cycleStepper.ComputeNext(primes[i]);
|
||||||
|
if (remainder != 0UL)
|
||||||
|
{
|
||||||
|
hits[i] = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
hits[i] = exponentStepper.ComputeNextIsUnity(primes[i]) ? (byte)1 : (byte)0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(nameof(MersenneCpuDivisorScanSession), "Divisor scan session disposed twice.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
ThreadStaticPools.ReturnMersenneCpuDivisorSession(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,25 @@
|
|||||||
using System;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Collections.Concurrent;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Numerics;
|
|
||||||
using PerfectNumbers.Core;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Cpu;
|
namespace PerfectNumbers.Core.Cpu;
|
||||||
|
|
||||||
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
|
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
|
||||||
{
|
{
|
||||||
private readonly object _sync = new();
|
private readonly object _sync = new();
|
||||||
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
|
|
||||||
private ulong _divisorLimit;
|
private ulong _divisorLimit;
|
||||||
private ulong _lastStatusDivisor;
|
private ulong _lastStatusDivisor;
|
||||||
private bool _isConfigured;
|
|
||||||
private int _batchSize = 1_024;
|
private int _batchSize = 1_024;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static GpuUInt128WorkSet _divisorScanGpuWorkSet;
|
||||||
|
|
||||||
|
private struct GpuUInt128WorkSet
|
||||||
|
{
|
||||||
|
public GpuUInt128 Step;
|
||||||
|
public GpuUInt128 Divisor;
|
||||||
|
public GpuUInt128 Limit;
|
||||||
|
}
|
||||||
|
|
||||||
public int BatchSize
|
public int BatchSize
|
||||||
{
|
{
|
||||||
@@ -23,71 +28,41 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void ConfigureFromMaxPrime(ulong maxPrime)
|
public void ConfigureFromMaxPrime(ulong maxPrime)
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
{
|
||||||
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
||||||
_lastStatusDivisor = 0UL;
|
_lastStatusDivisor = 0UL;
|
||||||
_isConfigured = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong DivisorLimit
|
public ulong DivisorLimit
|
||||||
{
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
if (!_isConfigured)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return _divisorLimit;
|
return _divisorLimit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public ulong GetAllowedMaxDivisor(ulong prime)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
{
|
public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
if (!_isConfigured)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
||||||
{
|
{
|
||||||
ulong allowedMax;
|
ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
if (!_isConfigured)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
|
||||||
}
|
|
||||||
|
|
||||||
allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allowedMax < 3UL)
|
if (allowedMax < 3UL)
|
||||||
{
|
{
|
||||||
|
// EvenPerfectBitScanner routes primes below the small-divisor cutoff to the GPU path, so the CPU path still sees
|
||||||
|
// trivial candidates during targeted tests. Short-circuit here to keep those runs aligned with the production flow.
|
||||||
divisorsExhausted = true;
|
divisorsExhausted = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong processedCount;
|
ulong processedCount;
|
||||||
ulong lastProcessed;
|
|
||||||
bool processedAll;
|
bool processedAll;
|
||||||
|
|
||||||
bool composite = CheckDivisors(
|
bool composite = CheckDivisors(
|
||||||
prime,
|
prime,
|
||||||
allowedMax,
|
allowedMax,
|
||||||
out lastProcessed,
|
|
||||||
out processedAll,
|
out processedAll,
|
||||||
out processedCount);
|
out processedCount);
|
||||||
|
|
||||||
@@ -109,26 +84,11 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||||
{
|
{
|
||||||
if (allowedMaxValues.Length < primes.Length)
|
ulong divisorLimit = _divisorLimit;
|
||||||
{
|
int length = primes.Length;
|
||||||
throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues));
|
for (int index = 0; index < length; index++)
|
||||||
}
|
|
||||||
|
|
||||||
ulong divisorLimit;
|
|
||||||
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
if (!_isConfigured)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
|
||||||
}
|
|
||||||
|
|
||||||
divisorLimit = _divisorLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int index = 0; index < primes.Length; index++)
|
|
||||||
{
|
{
|
||||||
allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit);
|
allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit);
|
||||||
}
|
}
|
||||||
@@ -136,85 +96,89 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
||||||
{
|
{
|
||||||
lock (_sync)
|
MersenneCpuDivisorScanSession? session = ThreadStaticPools.RentMersenneCpuDivisorSession();
|
||||||
|
if (session is not null)
|
||||||
{
|
{
|
||||||
if (!_isConfigured)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_sessionPool.TryTake(out DivisorScanSession? session))
|
|
||||||
{
|
|
||||||
session.Reset();
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DivisorScanSession(this);
|
return new MersenneCpuDivisorScanSession();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReturnSession(DivisorScanSession session)
|
|
||||||
{
|
|
||||||
_sessionPool.Add(session);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CheckDivisors(
|
private bool CheckDivisors(
|
||||||
ulong prime,
|
ulong prime,
|
||||||
ulong allowedMax,
|
ulong allowedMax,
|
||||||
out ulong lastProcessed,
|
|
||||||
out bool processedAll,
|
out bool processedAll,
|
||||||
out ulong processedCount)
|
out ulong processedCount)
|
||||||
{
|
{
|
||||||
lastProcessed = 0UL;
|
|
||||||
processedCount = 0UL;
|
processedCount = 0UL;
|
||||||
processedAll = false;
|
processedAll = false;
|
||||||
|
|
||||||
if (allowedMax < 3UL)
|
// The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs.
|
||||||
{
|
// Keeping the guard commented out documents the reasoning for benchmarks and tests.
|
||||||
return false;
|
// if (allowedMax < 3UL)
|
||||||
}
|
// {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
UInt128 step = (UInt128)prime << 1;
|
ref GpuUInt128WorkSet workSet = ref _divisorScanGpuWorkSet;
|
||||||
if (step == UInt128.Zero)
|
|
||||||
{
|
|
||||||
processedAll = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
UInt128 limit = allowedMax;
|
ref GpuUInt128 step = ref workSet.Step;
|
||||||
UInt128 divisor = step + UInt128.One;
|
step.High = 0UL;
|
||||||
if (divisor > limit)
|
step.Low = prime;
|
||||||
|
step.ShiftLeft(1);
|
||||||
|
|
||||||
|
ref GpuUInt128 limit = ref workSet.Limit;
|
||||||
|
limit.High = 0UL;
|
||||||
|
limit.Low = allowedMax;
|
||||||
|
|
||||||
|
ref GpuUInt128 divisor = ref workSet.Divisor;
|
||||||
|
divisor.High = step.High;
|
||||||
|
divisor.Low = step.Low;
|
||||||
|
divisor.Add(1UL);
|
||||||
|
if (divisor.CompareTo(limit) > 0)
|
||||||
{
|
{
|
||||||
processedAll = true;
|
processedAll = true;
|
||||||
|
divisor.High = 0UL;
|
||||||
|
divisor.Low = 0UL;
|
||||||
|
step.High = 0UL;
|
||||||
|
step.Low = 0UL;
|
||||||
|
limit.High = 0UL;
|
||||||
|
limit.Low = 0UL;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FactorCacheLease factorCacheLease = new FactorCacheLease();
|
||||||
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? factorCache = null;
|
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? factorCache = null;
|
||||||
|
|
||||||
DivisorCycleCache cycleCache = DivisorCycleCache.Shared;
|
DivisorCycleCache cycleCache = DivisorCycleCache.Shared;
|
||||||
|
|
||||||
byte step10 = (byte)(step % 10UL);
|
ulong stepHigh = step.High;
|
||||||
byte step8 = (byte)(step % 8UL);
|
ulong stepLow = step.Low;
|
||||||
byte step5 = (byte)(step % 5UL);
|
byte step10 = (byte)((((stepHigh % 10UL) * 6UL) + (stepLow % 10UL)) % 10UL);
|
||||||
byte step3 = (byte)(step % 3UL);
|
byte step8 = (byte)(stepLow % 8UL);
|
||||||
byte step7 = (byte)(step % 7UL);
|
byte step5 = (byte)(((stepHigh % 5UL) + (stepLow % 5UL)) % 5UL);
|
||||||
byte step11 = (byte)(step % 11UL);
|
byte step3 = (byte)(((stepHigh % 3UL) + (stepLow % 3UL)) % 3UL);
|
||||||
|
byte step7 = (byte)((((stepHigh % 7UL) * 2UL) + (stepLow % 7UL)) % 7UL);
|
||||||
|
byte step11 = (byte)((((stepHigh % 11UL) * 5UL) + (stepLow % 11UL)) % 11UL);
|
||||||
|
|
||||||
byte remainder10 = (byte)(divisor % 10UL);
|
ulong divisorHigh = divisor.High;
|
||||||
byte remainder8 = (byte)(divisor % 8UL);
|
ulong divisorLow = divisor.Low;
|
||||||
byte remainder5 = (byte)(divisor % 5UL);
|
byte remainder10 = (byte)((((divisorHigh % 10UL) * 6UL) + (divisorLow % 10UL)) % 10UL);
|
||||||
byte remainder3 = (byte)(divisor % 3UL);
|
byte remainder8 = (byte)(divisorLow % 8UL);
|
||||||
byte remainder7 = (byte)(divisor % 7UL);
|
byte remainder5 = (byte)(((divisorHigh % 5UL) + (divisorLow % 5UL)) % 5UL);
|
||||||
byte remainder11 = (byte)(divisor % 11UL);
|
byte remainder3 = (byte)(((divisorHigh % 3UL) + (divisorLow % 3UL)) % 3UL);
|
||||||
|
byte remainder7 = (byte)((((divisorHigh % 7UL) * 2UL) + (divisorLow % 7UL)) % 7UL);
|
||||||
|
byte remainder11 = (byte)((((divisorHigh % 11UL) * 5UL) + (divisorLow % 11UL)) % 11UL);
|
||||||
|
|
||||||
// Keep the divisibility filters aligned with the divisor-cycle generator so the
|
// Keep the divisibility filters aligned with the divisor-cycle generator so the
|
||||||
// CPU path never requests cycles that were skipped during cache creation.
|
// CPU path never requests cycles that were skipped during cache creation.
|
||||||
bool lastIsSeven = (prime & 3UL) == 3UL;
|
bool lastIsSeven = (prime & 3UL) == 3UL;
|
||||||
|
|
||||||
while (divisor <= limit)
|
while (divisor.CompareTo(limit) <= 0)
|
||||||
{
|
{
|
||||||
ulong candidate = (ulong)divisor;
|
ulong candidate = divisor.Low;
|
||||||
processedCount++;
|
processedCount++;
|
||||||
lastProcessed = candidate;
|
|
||||||
|
|
||||||
bool admissible = lastIsSeven
|
bool admissible = lastIsSeven
|
||||||
? (remainder10 == 3 || remainder10 == 7 || remainder10 == 9)
|
? (remainder10 == 3 || remainder10 == 7 || remainder10 == 9)
|
||||||
@@ -224,37 +188,55 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
{
|
{
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(candidate);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(candidate);
|
||||||
ulong divisorCycle;
|
ulong divisorCycle;
|
||||||
|
|
||||||
if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles)
|
if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||||
{
|
{
|
||||||
divisorCycle = cycleCache.GetCycleLength(candidate);
|
divisorCycle = cycleCache.GetCycleLength(candidate);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
factorCache ??= new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(8);
|
if (factorCache is null)
|
||||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(candidate, prime, divisorData, factorCache, out divisorCycle) || divisorCycle == 0UL)
|
{
|
||||||
|
factorCacheLease.EnsureInitialized(ref factorCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(
|
||||||
|
candidate,
|
||||||
|
prime,
|
||||||
|
divisorData,
|
||||||
|
factorCache,
|
||||||
|
out ulong computedCycle) || computedCycle == 0UL)
|
||||||
{
|
{
|
||||||
divisorCycle = cycleCache.GetCycleLength(candidate);
|
divisorCycle = cycleCache.GetCycleLength(candidate);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
divisorCycle = computedCycle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (divisorCycle != 0 && divisorCycle == prime)
|
if (divisorCycle == prime)
|
||||||
{
|
{
|
||||||
|
// A cycle equal to the tested exponent (which is prime in this path) guarantees that the candidate divides
|
||||||
|
// the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p.
|
||||||
|
factorCacheLease.Dispose();
|
||||||
|
factorCache = null;
|
||||||
|
divisor.High = 0UL;
|
||||||
|
divisor.Low = 0UL;
|
||||||
|
step.High = 0UL;
|
||||||
|
step.Low = 0UL;
|
||||||
|
limit.High = 0UL;
|
||||||
|
limit.Low = 0UL;
|
||||||
processedAll = true;
|
processedAll = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (divisorCycle == 0)
|
|
||||||
|
if (divisorCycle == 0UL)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Divisor cycle wasn't calculated for ${prime}");
|
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||||
}
|
}
|
||||||
// if (divisorCycle != 0UL && CheckDivisor(prime, divisorCycle, divisorData) != 0)
|
|
||||||
// {
|
|
||||||
// processedAll = true;
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
divisor += step;
|
divisor.Add(step);
|
||||||
remainder10 = AddMod(remainder10, step10, (byte)10);
|
remainder10 = AddMod(remainder10, step10, (byte)10);
|
||||||
remainder8 = AddMod(remainder8, step8, (byte)8);
|
remainder8 = AddMod(remainder8, step8, (byte)8);
|
||||||
remainder5 = AddMod(remainder5, step5, (byte)5);
|
remainder5 = AddMod(remainder5, step5, (byte)5);
|
||||||
@@ -263,13 +245,22 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
remainder11 = AddMod(remainder11, step11, (byte)11);
|
remainder11 = AddMod(remainder11, step11, (byte)11);
|
||||||
}
|
}
|
||||||
|
|
||||||
processedAll = divisor > limit;
|
factorCacheLease.Dispose();
|
||||||
|
factorCache = null; // The pooled dictionary was returned to the thread-static slot.
|
||||||
|
|
||||||
|
processedAll = divisor.CompareTo(limit) > 0;
|
||||||
|
divisor.High = 0UL;
|
||||||
|
divisor.Low = 0UL;
|
||||||
|
step.High = 0UL;
|
||||||
|
step.Low = 0UL;
|
||||||
|
limit.High = 0UL;
|
||||||
|
limit.Low = 0UL;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
|
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
ulong residue = prime.Pow2MontgomeryModWithCycle(divisorCycle, divisorData);
|
ulong residue = prime.Pow2MontgomeryModWithCycleCpu(divisorCycle, divisorData);
|
||||||
return residue == 1UL ? (byte)1 : (byte)0;
|
return residue == 1UL ? (byte)1 : (byte)0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,90 +325,5 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit);
|
return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
|
||||||
{
|
|
||||||
private readonly MersenneNumberDivisorByDivisorCpuTester _owner;
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
internal DivisorScanSession(MersenneNumberDivisorByDivisorCpuTester owner)
|
|
||||||
{
|
|
||||||
_owner = owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Reset()
|
|
||||||
{
|
|
||||||
_disposed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
{
|
|
||||||
throw new ObjectDisposedException(nameof(DivisorScanSession));
|
|
||||||
}
|
|
||||||
|
|
||||||
int length = primes.Length;
|
|
||||||
if (length == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MontgomeryDivisorData cachedData = divisorData;
|
|
||||||
if (cachedData.Modulus != divisor)
|
|
||||||
{
|
|
||||||
cachedData = MontgomeryDivisorDataCache.Get(divisor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (divisorCycle == 0UL)
|
|
||||||
{
|
|
||||||
divisorCycle = DivisorCycleCache.Shared.GetCycleLength(divisor);
|
|
||||||
if (divisorCycle == 0UL)
|
|
||||||
{
|
|
||||||
hits.Clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep these remainder steppers in place so future updates continue reusing the previously computed residues.
|
|
||||||
// They are critical for avoiding repeated full Montgomery exponentiation work when scanning divisors.
|
|
||||||
var exponentStepper = new ExponentRemainderStepper(cachedData);
|
|
||||||
if (!exponentStepper.IsValidModulus)
|
|
||||||
{
|
|
||||||
hits.Clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cycleStepper = new CycleRemainderStepper(divisorCycle);
|
|
||||||
|
|
||||||
ulong remainder = cycleStepper.Initialize(primes[0]);
|
|
||||||
hits[0] = remainder == 0UL
|
|
||||||
? (exponentStepper.ComputeNextIsUnity(primes[0]) ? (byte)1 : (byte)0)
|
|
||||||
: (byte)0;
|
|
||||||
|
|
||||||
for (int i = 1; i < length; i++)
|
|
||||||
{
|
|
||||||
remainder = cycleStepper.ComputeNext(primes[i]);
|
|
||||||
if (remainder != 0UL)
|
|
||||||
{
|
|
||||||
hits[i] = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
hits[i] = exponentStepper.ComputeNextIsUnity(primes[i]) ? (byte)1 : (byte)0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
_owner.ReturnSession(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
using System;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
internal struct CycleRemainderStepper
|
public struct CycleRemainderStepper
|
||||||
{
|
{
|
||||||
private readonly ulong _cycleLength;
|
private readonly ulong _cycleLength;
|
||||||
private ulong _previousPrime;
|
private ulong _previousPrime;
|
||||||
@@ -23,6 +22,7 @@ internal struct CycleRemainderStepper
|
|||||||
_hasState = false;
|
_hasState = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
// TODO: Inline this reset at the call sites so the hot loops reuse struct reinitialization
|
// TODO: Inline this reset at the call sites so the hot loops reuse struct reinitialization
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
using System;
|
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
|
||||||
using ILGPU;
|
using ILGPU;
|
||||||
using ILGPU.Runtime;
|
using ILGPU.Runtime;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ internal struct ExponentRemainderStepper
|
|||||||
// TODO: Route these state resets through the ProcessEightBitWindows helper once the scalar
|
// TODO: Route these state resets through the ProcessEightBitWindows helper once the scalar
|
||||||
// Pow2MontgomeryMod implementation adopts it so delta stepping inherits the benchmarked
|
// Pow2MontgomeryMod implementation adopts it so delta stepping inherits the benchmarked
|
||||||
// 2× gains for large exponents.
|
// 2× gains for large exponents.
|
||||||
_currentMontgomery = exponent.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
_hasState = true;
|
_hasState = true;
|
||||||
return ReduceCurrent();
|
return ReduceCurrent();
|
||||||
@@ -59,7 +59,7 @@ internal struct ExponentRemainderStepper
|
|||||||
// cached Montgomery residue ladder highlighted in MersenneDivisorCycleLengthGpuBenchmarks.
|
// cached Montgomery residue ladder highlighted in MersenneDivisorCycleLengthGpuBenchmarks.
|
||||||
// TODO: Replace this per-delta powmod with the upcoming windowed ladder so incremental
|
// TODO: Replace this per-delta powmod with the upcoming windowed ladder so incremental
|
||||||
// updates stop paying the single-bit cost highlighted in GpuPow2ModBenchmarks.
|
// 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);
|
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
return ReduceCurrent();
|
return ReduceCurrent();
|
||||||
@@ -77,7 +77,7 @@ internal struct ExponentRemainderStepper
|
|||||||
{
|
{
|
||||||
// TODO: Switch this reload to the shared windowed pow2 helper once available so CPU
|
// TODO: Switch this reload to the shared windowed pow2 helper once available so CPU
|
||||||
// residue checks align with the optimized ProcessEightBitWindows timings.
|
// residue checks align with the optimized ProcessEightBitWindows timings.
|
||||||
_currentMontgomery = exponent.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
_hasState = true;
|
_hasState = true;
|
||||||
return _currentMontgomery == _montgomeryOne;
|
return _currentMontgomery == _montgomeryOne;
|
||||||
@@ -88,7 +88,7 @@ internal struct ExponentRemainderStepper
|
|||||||
// lookups so this branch also avoids recomputing powmods when the snapshot lacks the divisor.
|
// 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
|
// 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.
|
// 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);
|
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
return _currentMontgomery == _montgomeryOne;
|
return _currentMontgomery == _montgomeryOne;
|
||||||
@@ -123,7 +123,7 @@ internal struct ExponentRemainderStepper
|
|||||||
{
|
{
|
||||||
// TODO: Replace this fallback path with the upcoming ProcessEightBitWindows helper so
|
// TODO: Replace this fallback path with the upcoming ProcessEightBitWindows helper so
|
||||||
// fresh Montgomery states also benefit from the faster pow2 ladder measured on CPUs.
|
// 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;
|
_previousExponent = exponent;
|
||||||
_hasState = true;
|
_hasState = true;
|
||||||
isUnity = _currentMontgomery == _montgomeryOne;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
|
public readonly struct FactorEntry128(in UInt128 value, int exponent)
|
||||||
|
{
|
||||||
|
public readonly UInt128 Value = value;
|
||||||
|
|
||||||
|
public readonly int Exponent = exponent;
|
||||||
|
}
|
||||||
@@ -126,7 +126,7 @@ public static class GpuContextPool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct GpuContextLease : IDisposable
|
public struct GpuContextLease
|
||||||
{
|
{
|
||||||
private readonly PooledContext _ctx;
|
private readonly PooledContext _ctx;
|
||||||
private bool disposed;
|
private bool disposed;
|
||||||
|
|||||||
@@ -677,7 +677,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
|||||||
// GC.SuppressFinalize(this);
|
// GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly struct ExecutionScope : IDisposable
|
public readonly struct ExecutionScope
|
||||||
{
|
{
|
||||||
private readonly object? _lock;
|
private readonly object? _lock;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Gpu;
|
namespace PerfectNumbers.Core.Gpu;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using ILGPU.Algorithms;
|
using ILGPU.Algorithms;
|
||||||
@@ -267,7 +266,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void Add(in ReadOnlyGpuUInt128 other) => AddInternal(other.High, other.Low);
|
public void Add(in ReadOnlyGpuUInt128 other) => AddInternal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Add(in GpuUInt128 other) => AddInternal(other.High, other.Low);
|
public void Add(GpuUInt128 other) => AddInternal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void AddInternal(ulong otherHigh, ulong otherLow)
|
private void AddInternal(ulong otherHigh, ulong otherLow)
|
||||||
@@ -294,17 +293,17 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void AddMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
public void AddMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(in GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
public void AddMod(GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(in ReadOnlyGpuUInt128 value, in GpuUInt128 modulus)
|
public void AddMod(in ReadOnlyGpuUInt128 value, GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||||
AddModInternal(value.High, value.Low, in readOnlyMod);
|
AddModInternal(value.High, value.Low, in readOnlyMod);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(in GpuUInt128 value, in GpuUInt128 modulus)
|
public void AddMod(GpuUInt128 value, GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||||
AddModInternal(value.High, value.Low, in readOnlyMod);
|
AddModInternal(value.High, value.Low, in readOnlyMod);
|
||||||
@@ -390,7 +389,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(in GpuUInt128 value, ulong modulus) => AddMod(value.Low, modulus);
|
public void AddMod(GpuUInt128 value, ulong modulus) => AddMod(value.Low, modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(ulong value, ulong modulus)
|
public void AddMod(ulong value, ulong modulus)
|
||||||
@@ -414,7 +413,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void Sub(in ReadOnlyGpuUInt128 other) => SubInternal(other.High, other.Low);
|
public void Sub(in ReadOnlyGpuUInt128 other) => SubInternal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Sub(in GpuUInt128 other) => SubInternal(other.High, other.Low);
|
public void Sub(GpuUInt128 other) => SubInternal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void SubInternal(ulong otherHigh, ulong otherLow)
|
private void SubInternal(ulong otherHigh, ulong otherLow)
|
||||||
@@ -461,7 +460,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void Mul64(in ReadOnlyGpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
public void Mul64(in ReadOnlyGpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Mul64(in GpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
public void Mul64(GpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void Mul64Internal(ulong otherHigh, ulong otherLow)
|
private void Mul64Internal(ulong otherHigh, ulong otherLow)
|
||||||
@@ -588,7 +587,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static int TrailingZeroCount(in GpuUInt128 value)
|
public static int TrailingZeroCount(GpuUInt128 value)
|
||||||
{
|
{
|
||||||
if (value.Low != 0UL)
|
if (value.Low != 0UL)
|
||||||
{
|
{
|
||||||
@@ -646,17 +645,17 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void SubMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
public void SubMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void SubMod(in GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
public void SubMod(GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void SubMod(in ReadOnlyGpuUInt128 value, in GpuUInt128 modulus)
|
public void SubMod(in ReadOnlyGpuUInt128 value, GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||||
SubModInternal(value.High, value.Low, in readOnlyMod);
|
SubModInternal(value.High, value.Low, in readOnlyMod);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void SubMod(in GpuUInt128 value, in GpuUInt128 modulus)
|
public void SubMod(GpuUInt128 value, GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||||
SubModInternal(value.High, value.Low, in readOnlyMod);
|
SubModInternal(value.High, value.Low, in readOnlyMod);
|
||||||
@@ -727,7 +726,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Mul(in GpuUInt128 other)
|
public void Mul(GpuUInt128 other)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyThis = AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyThis = AsReadOnly();
|
||||||
ReadOnlyGpuUInt128 readOnlyOther = other.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyOther = other.AsReadOnly();
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
using System;
|
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Numerics;
|
|
||||||
using ILGPU;
|
using ILGPU;
|
||||||
using ILGPU.Runtime;
|
using ILGPU.Runtime;
|
||||||
using MontgomeryDivisorData = PerfectNumbers.Core.MontgomeryDivisorData;
|
|
||||||
using MontgomeryDivisorDataCache = PerfectNumbers.Core.MontgomeryDivisorDataCache;
|
|
||||||
using CycleRemainderStepper = PerfectNumbers.Core.CycleRemainderStepper;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Gpu;
|
namespace PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
@@ -148,7 +143,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||||
{
|
{
|
||||||
if (allowedMaxValues.Length < primes.Length)
|
if (allowedMaxValues.Length < primes.Length)
|
||||||
{
|
{
|
||||||
@@ -551,7 +546,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong exponent = exponents[index];
|
ulong exponent = exponents[index];
|
||||||
hits[index] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL ? (byte)1 : (byte)0;
|
hits[index] = ULongExtensions.Pow2MontgomeryModWindowedKernel(exponent, divisor, keepMontgomery: false) == 1UL ? (byte)1 : (byte)0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||||
@@ -583,7 +578,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
_disposed = false;
|
_disposed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
|
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
@@ -770,7 +765,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
ulong exponent = exponents[index];
|
ulong exponent = exponents[index];
|
||||||
|
|
||||||
results[index] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: true);
|
results[index] = ULongExtensions.Pow2MontgomeryModWindowedKernel(exponent, divisor, keepMontgomery: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong DivisorLimit
|
public ulong DivisorLimit
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
using System;
|
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Threading;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using ILGPU;
|
|
||||||
using ILGPU.Runtime;
|
using ILGPU.Runtime;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Gpu;
|
namespace PerfectNumbers.Core.Gpu;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
|
||||||
using ILGPU.Runtime;
|
using ILGPU.Runtime;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Gpu;
|
namespace PerfectNumbers.Core.Gpu;
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
using System;
|
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using ILGPU;
|
using ILGPU;
|
||||||
using ILGPU.Runtime;
|
using ILGPU.Runtime;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using PerfectNumbers.Core;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using ILGPU.Runtime.OpenCL;
|
using ILGPU.Runtime.OpenCL;
|
||||||
@@ -415,9 +413,9 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
ulong? previousOrder,
|
ulong? previousOrder,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig config,
|
PrimeOrderCalculator.PrimeOrderSearchConfig config,
|
||||||
in MontgomeryDivisorData divisorData,
|
in MontgomeryDivisorData divisorData,
|
||||||
out PrimeOrderCalculator.PrimeOrderResult result)
|
out ulong order)
|
||||||
{
|
{
|
||||||
result = default;
|
order = 0UL;
|
||||||
|
|
||||||
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||||
var execution = lease.EnterExecutionScope();
|
var execution = lease.EnterExecutionScope();
|
||||||
@@ -497,18 +495,17 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
throw new InvalidOperationException("GPU Pollard Rho stack overflow; increase HeuristicStackCapacity.");
|
throw new InvalidOperationException("GPU Pollard Rho stack overflow; increase HeuristicStackCapacity.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong order = 0UL;
|
|
||||||
resultBuffer.View.CopyToCPU(ref order, 1);
|
resultBuffer.View.CopyToCPU(ref order, 1);
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderStatus finalStatus = kernelStatus == PrimeOrderKernelStatus.Found
|
if (kernelStatus == PrimeOrderKernelStatus.FactoringFailure)
|
||||||
? PrimeOrderCalculator.PrimeOrderStatus.Found
|
{
|
||||||
: PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved;
|
order = 0UL;
|
||||||
|
}
|
||||||
|
|
||||||
result = new PrimeOrderCalculator.PrimeOrderResult(finalStatus, order);
|
|
||||||
DisposeResources();
|
DisposeResources();
|
||||||
lease.Dispose();
|
lease.Dispose();
|
||||||
|
|
||||||
return true;
|
return order != 0UL;
|
||||||
|
|
||||||
void DisposeResources()
|
void DisposeResources()
|
||||||
{
|
{
|
||||||
@@ -812,7 +809,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong reduced = phi / factor;
|
ulong reduced = phi / factor;
|
||||||
if (reduced.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL)
|
if (ULongExtensions.Pow2MontgomeryModWindowedKernel(reduced, divisor, keepMontgomery: false) == 1UL)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1596,7 +1593,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
|
|
||||||
private static bool Pow2EqualsOneKernel(ulong exponent, in MontgomeryDivisorData divisor)
|
private static bool Pow2EqualsOneKernel(ulong exponent, in MontgomeryDivisorData divisor)
|
||||||
{
|
{
|
||||||
return exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL;
|
return ULongExtensions.Pow2MontgomeryModWindowedKernel(exponent, divisor, keepMontgomery: false) == 1UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong CalculateByDoublingKernel(ulong prime)
|
private static ulong CalculateByDoublingKernel(ulong prime)
|
||||||
@@ -1796,7 +1793,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
return 0UL;
|
return 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false);
|
return ULongExtensions.Pow2MontgomeryModWindowedKernel(exponent, divisorData, keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> GetPow2ModKernel(Accelerator accelerator)
|
private static Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> GetPow2ModKernel(Accelerator accelerator)
|
||||||
@@ -1812,7 +1809,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
private static void Pow2ModKernel(Index1D index, ArrayView1D<ulong, Stride1D.Dense> exponents, MontgomeryDivisorData divisor, ArrayView1D<ulong, Stride1D.Dense> remainders)
|
private static void Pow2ModKernel(Index1D index, ArrayView1D<ulong, Stride1D.Dense> exponents, MontgomeryDivisorData divisor, ArrayView1D<ulong, Stride1D.Dense> remainders)
|
||||||
{
|
{
|
||||||
ulong exponent = exponents[index];
|
ulong exponent = exponents[index];
|
||||||
remainders[index] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false);
|
remainders[index] = ULongExtensions.Pow2MontgomeryModWindowedKernel(exponent, divisor, keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>> GetPow2ModWideKernel(Accelerator accelerator)
|
private static Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>> GetPow2ModWideKernel(Accelerator accelerator)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
public interface IMersenneNumberDivisorByDivisorTester
|
public interface IMersenneNumberDivisorByDivisorTester
|
||||||
@@ -16,10 +14,10 @@ public interface IMersenneNumberDivisorByDivisorTester
|
|||||||
|
|
||||||
IDivisorScanSession CreateDivisorSession();
|
IDivisorScanSession CreateDivisorSession();
|
||||||
|
|
||||||
void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues);
|
void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues);
|
||||||
|
|
||||||
public interface IDivisorScanSession : IDisposable
|
public interface IDivisorScanSession : IDisposable
|
||||||
{
|
{
|
||||||
void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits);
|
void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using ILGPU;
|
using ILGPU;
|
||||||
using ILGPU.Runtime;
|
using ILGPU.Runtime;
|
||||||
@@ -17,7 +15,6 @@ public class MersenneDivisorCycles
|
|||||||
private List<(ulong divisor, ulong cycleLength)> _table = [];
|
private List<(ulong divisor, ulong cycleLength)> _table = [];
|
||||||
// Lightweight read-mostly cache for small divisors (<= 4,000,000). 0 => unknown
|
// Lightweight read-mostly cache for small divisors (<= 4,000,000). 0 => unknown
|
||||||
private ulong[]? _smallCycles;
|
private ulong[]? _smallCycles;
|
||||||
|
|
||||||
public static MersenneDivisorCycles Shared { get; } = new MersenneDivisorCycles();
|
public static MersenneDivisorCycles Shared { get; } = new MersenneDivisorCycles();
|
||||||
|
|
||||||
public MersenneDivisorCycles()
|
public MersenneDivisorCycles()
|
||||||
@@ -102,7 +99,7 @@ public class MersenneDivisorCycles
|
|||||||
return cycleLength == exponent;
|
return cycleLength == exponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false) == 1UL;
|
return exponent.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool CycleEqualsExponentForMersenneCandidate(ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
public static bool CycleEqualsExponentForMersenneCandidate(ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
||||||
@@ -126,7 +123,7 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false) == 1UL;
|
return exponent.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsValidMersenneDivisorCandidate(ulong divisor, ulong exponent)
|
private static bool IsValidMersenneDivisorCandidate(ulong divisor, ulong exponent)
|
||||||
@@ -234,14 +231,14 @@ public class MersenneDivisorCycles
|
|||||||
return (UInt128)cycle;
|
return (UInt128)cycle;
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderResultWide wideResult = PrimeOrderCalculator.Calculate(
|
UInt128 wideOrder = PrimeOrderCalculator.Calculate(
|
||||||
divisor,
|
divisor,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
||||||
if (wideResult.Order != UInt128.Zero)
|
if (wideOrder != UInt128.Zero)
|
||||||
{
|
{
|
||||||
return wideResult.Order;
|
return wideOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, find order of 2 mod divisor
|
// Otherwise, find order of 2 mod divisor
|
||||||
@@ -285,23 +282,25 @@ public class MersenneDivisorCycles
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimeOrderCalculator.PrimeOrderResult orderResult = PrimeOrderCalculator.Calculate(
|
ulong computedOrder = PrimeOrderCalculator.Calculate(
|
||||||
divisor,
|
divisor,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
divisorData,
|
divisorData,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
||||||
if (orderResult.Order != 0UL)
|
if (computedOrder != 0UL)
|
||||||
{
|
{
|
||||||
cycleLength = orderResult.Order;
|
cycleLength = computedOrder;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong phi = divisor - 1UL;
|
ulong phi = divisor - 1UL;
|
||||||
if (phi == 0UL)
|
// A zero totient would imply divisor == 0, which never occurs on the tested path. Keep the guard documented without
|
||||||
{
|
// branching in the hot loop.
|
||||||
return false;
|
// if (phi == 0UL)
|
||||||
}
|
// {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
int twoCount = BitOperations.TrailingZeroCount(phi);
|
int twoCount = BitOperations.TrailingZeroCount(phi);
|
||||||
ulong reducedPhi = phi >> twoCount;
|
ulong reducedPhi = phi >> twoCount;
|
||||||
@@ -311,24 +310,32 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong k = reducedPhi / exponent;
|
ulong k = reducedPhi / exponent;
|
||||||
if (k == 0UL)
|
// k == 0 would indicate reducedPhi < exponent, which the EvenPerfectBitScanner call flow prevents. Document the
|
||||||
{
|
// assumption instead of branching.
|
||||||
return false;
|
// if (k == 0UL)
|
||||||
}
|
// {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
Dictionary<ulong, int> factorCounts = ThreadStaticPools.RentFactorCountDictionary();
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
Dictionary<ulong, int> factorCounts = new(8);
|
|
||||||
if (twoCount > 0)
|
if (twoCount > 0)
|
||||||
{
|
{
|
||||||
factorCounts[2UL] = twoCount;
|
factorCounts[2UL] = twoCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true) || !AccumulateFactors(k, factorCache, factorCounts, cacheResult: false))
|
bool exponentAccumulated = AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true);
|
||||||
|
bool factorsAccumulated = exponentAccumulated && AccumulateFactors(k, factorCache, factorCounts, cacheResult: false);
|
||||||
|
|
||||||
|
if (factorsAccumulated)
|
||||||
{
|
{
|
||||||
return false;
|
cycleLength = ReduceOrder(divisorData, phi, factorCounts);
|
||||||
|
success = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
cycleLength = ReduceOrder(divisorData, phi, factorCounts);
|
ThreadStaticPools.ReturnFactorCountDictionary(factorCounts);
|
||||||
return true;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool AccumulateFactors(
|
private static bool AccumulateFactors(
|
||||||
@@ -342,18 +349,27 @@ public class MersenneDivisorCycles
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryGetFactorization(value, cache, cacheResult, out FactorCacheEntry factorization))
|
if (!TryGetFactorization(value, cache, cacheResult, out FactorCacheEntry? factorization, out bool releaseAfterUse) || factorization is null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong[] primes = factorization.Primes;
|
FactorCacheEntry actualFactorization = factorization;
|
||||||
byte[] exponents = factorization.Exponents;
|
KeyValuePair<ulong, int>[]? pairs = actualFactorization.Pairs;
|
||||||
int length = factorization.Count;
|
if (pairs is not null)
|
||||||
|
{
|
||||||
|
int length = actualFactorization.Count;
|
||||||
|
|
||||||
for (int i = 0; i < length; i++)
|
for (int i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
AddFactor(counts, primes[i], exponents[i]);
|
KeyValuePair<ulong, int> pair = pairs[i];
|
||||||
|
AddFactor(counts, pair.Key, pair.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releaseAfterUse)
|
||||||
|
{
|
||||||
|
actualFactorization.ReturnToPool();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -363,37 +379,57 @@ public class MersenneDivisorCycles
|
|||||||
ulong value,
|
ulong value,
|
||||||
Dictionary<ulong, FactorCacheEntry>? cache,
|
Dictionary<ulong, FactorCacheEntry>? cache,
|
||||||
bool cacheResult,
|
bool cacheResult,
|
||||||
out FactorCacheEntry factorization)
|
out FactorCacheEntry? factorization,
|
||||||
|
out bool releaseAfterUse)
|
||||||
{
|
{
|
||||||
if (cache is not null && cache.TryGetValue(value, out factorization))
|
releaseAfterUse = false;
|
||||||
|
|
||||||
|
if (cache is not null && cache.TryGetValue(value, out FactorCacheEntry? cachedEntry))
|
||||||
{
|
{
|
||||||
|
factorization = cachedEntry;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary<ulong, int> scratch = new(8);
|
Dictionary<ulong, int> scratch = ThreadStaticPools.RentFactorScratchDictionary();
|
||||||
if (!TryFactorIntoCountsInternal(value, scratch))
|
bool factored = TryFactorIntoCountsInternal(value, scratch);
|
||||||
|
if (!factored)
|
||||||
{
|
{
|
||||||
factorization = default!;
|
ThreadStaticPools.ReturnFactorScratchDictionary(scratch);
|
||||||
|
factorization = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = scratch.Count;
|
int count = scratch.Count;
|
||||||
ulong[] primes = new ulong[count];
|
FactorCacheEntry entry = ThreadStaticPools.RentFactorCacheEntry();
|
||||||
byte[] exponents = new byte[count];
|
if (count == 0)
|
||||||
int index = 0;
|
|
||||||
foreach (KeyValuePair<ulong, int> entry in scratch)
|
|
||||||
{
|
{
|
||||||
primes[index] = entry.Key;
|
entry.Initialize(null, 0, fromPool: false);
|
||||||
exponents[index] = checked((byte)entry.Value);
|
factorization = entry;
|
||||||
index++;
|
releaseAfterUse = cache is null || !cacheResult;
|
||||||
|
if (cache is not null && cacheResult)
|
||||||
|
{
|
||||||
|
cache[value] = entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
Array.Sort(primes, exponents);
|
ThreadStaticPools.ReturnFactorScratchDictionary(scratch);
|
||||||
factorization = new FactorCacheEntry(primes, exponents, count);
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyValuePair<ulong, int>[] pairs = ThreadStaticPools.FactorKeyValuePairPool.Rent(count);
|
||||||
|
((ICollection<KeyValuePair<ulong, int>>)scratch).CopyTo(pairs, 0);
|
||||||
|
ThreadStaticPools.ReturnFactorScratchDictionary(scratch);
|
||||||
|
Array.Sort(pairs, 0, count, FactorPairValueComparer.Instance);
|
||||||
|
|
||||||
|
entry.Initialize(pairs, count, fromPool: true);
|
||||||
|
factorization = entry;
|
||||||
|
|
||||||
if (cache is not null && cacheResult)
|
if (cache is not null && cacheResult)
|
||||||
{
|
{
|
||||||
cache[value] = factorization;
|
cache[value] = entry;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
releaseAfterUse = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -401,10 +437,11 @@ public class MersenneDivisorCycles
|
|||||||
|
|
||||||
private static bool TryFactorIntoCountsInternal(ulong value, Dictionary<ulong, int> counts)
|
private static bool TryFactorIntoCountsInternal(ulong value, Dictionary<ulong, int> counts)
|
||||||
{
|
{
|
||||||
if (value <= 1UL)
|
// The EvenPerfectBitScanner only requests factors for values >= 2 here. Leave the guard documented without executing it.
|
||||||
{
|
// if (value <= 1UL)
|
||||||
return true;
|
// {
|
||||||
}
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
ulong remaining = value;
|
ulong remaining = value;
|
||||||
uint[] smallPrimes = PrimesGenerator.SmallPrimes;
|
uint[] smallPrimes = PrimesGenerator.SmallPrimes;
|
||||||
@@ -419,6 +456,7 @@ public class MersenneDivisorCycles
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Reuse the remainder-tracking update from the cycle heuristic so repeated divisions disappear from this loop.
|
||||||
while ((remaining % prime) == 0UL)
|
while ((remaining % prime) == 0UL)
|
||||||
{
|
{
|
||||||
AddFactor(counts, prime);
|
AddFactor(counts, prime);
|
||||||
@@ -431,20 +469,43 @@ public class MersenneDivisorCycles
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Benchmark PrimeTester.IsPrimeInternal against Open.Numeric.Primes on the EvenPerfectBitScanner workloads to confirm the faster option for wide composites.
|
||||||
if (PrimeTester.IsPrimeInternal(remaining, CancellationToken.None))
|
if (PrimeTester.IsPrimeInternal(remaining, CancellationToken.None))
|
||||||
{
|
{
|
||||||
AddFactor(counts, remaining);
|
AddFactor(counts, remaining);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Evaluate folding the remainder-tracking mechanism into PollardRho64 so the walk leverages previously computed residues.
|
||||||
ulong factor = PollardRho64(remaining);
|
ulong factor = PollardRho64(remaining);
|
||||||
if (factor == 0UL || factor == remaining)
|
if (factor == 0UL || factor == remaining)
|
||||||
|
{
|
||||||
|
// Pollard-Rho can still stall on rare stubborn composites during test and benchmark runs, so keep the failure guard in place
|
||||||
|
// and let the caller fall back to the cached cycle calculator.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Investigate computing this quotient via remainder tracking or reciprocal multiplication to avoid the division.
|
||||||
|
ulong quotient = remaining / factor;
|
||||||
|
// Pollard-Rho never returns factors <= 1 on the EvenPerfectBitScanner path; keep the old guard documented
|
||||||
|
// for completeness without branching in the hot path.
|
||||||
|
// if (factor > 1UL && !TryFactorIntoCountsInternal(factor, counts))
|
||||||
|
// {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!TryFactorIntoCountsInternal(factor, counts))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong quotient = remaining / factor;
|
// Guard the recursive calls to terminate once Pollard-Rho reduces either branch to 1.
|
||||||
return TryFactorIntoCountsInternal(factor, counts) && TryFactorIntoCountsInternal(quotient, counts);
|
if (quotient > 1UL && !TryFactorIntoCountsInternal(quotient, counts))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddFactor(Dictionary<ulong, int> counts, ulong factor)
|
private static void AddFactor(Dictionary<ulong, int> counts, ulong factor)
|
||||||
@@ -505,7 +566,7 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong candidate = order / prime;
|
ulong candidate = order / prime;
|
||||||
if (candidate.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false) == 1UL)
|
if (candidate.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL)
|
||||||
{
|
{
|
||||||
order = candidate;
|
order = candidate;
|
||||||
continue;
|
continue;
|
||||||
@@ -599,19 +660,66 @@ public class MersenneDivisorCycles
|
|||||||
return BinaryPrimitives.ReadUInt64LittleEndian(buffer);
|
return BinaryPrimitives.ReadUInt64LittleEndian(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly struct FactorCacheEntry
|
private sealed class FactorPairValueComparer : IComparer<KeyValuePair<ulong, int>>
|
||||||
{
|
{
|
||||||
public FactorCacheEntry(ulong[] primes, byte[] exponents, int count)
|
public static readonly FactorPairValueComparer Instance = new FactorPairValueComparer();
|
||||||
|
|
||||||
|
private FactorPairValueComparer()
|
||||||
{
|
{
|
||||||
Primes = primes;
|
|
||||||
Exponents = exponents;
|
|
||||||
Count = count;
|
|
||||||
}
|
}
|
||||||
public ulong[] Primes { get; }
|
|
||||||
|
|
||||||
public byte[] Exponents { get; }
|
public int Compare(KeyValuePair<ulong, int> x, KeyValuePair<ulong, int> y)
|
||||||
|
{
|
||||||
|
if (x.Key < y.Key)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
public int Count { get; }
|
if (x.Key > y.Key)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class FactorCacheEntry
|
||||||
|
{
|
||||||
|
internal FactorCacheEntry? Next;
|
||||||
|
internal KeyValuePair<ulong, int>[]? Pairs;
|
||||||
|
internal int Count;
|
||||||
|
private bool _fromPool;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal void Reset()
|
||||||
|
{
|
||||||
|
Pairs = null;
|
||||||
|
Count = 0;
|
||||||
|
_fromPool = false;
|
||||||
|
Next = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Initialize(KeyValuePair<ulong, int>[]? pairs, int count, bool fromPool)
|
||||||
|
{
|
||||||
|
Pairs = pairs;
|
||||||
|
Count = count;
|
||||||
|
_fromPool = fromPool;
|
||||||
|
Next = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal void ReturnToPool()
|
||||||
|
{
|
||||||
|
KeyValuePair<ulong, int>[]? pairs = Pairs;
|
||||||
|
if (_fromPool && pairs is not null)
|
||||||
|
{
|
||||||
|
ThreadStaticPools.FactorKeyValuePairPool.Return(pairs, clearArray: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Reset();
|
||||||
|
ThreadStaticPools.ReturnFactorCacheEntry(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static (long nextPosition, long completeCount) FindLast(string path)
|
public static (long nextPosition, long completeCount) FindLast(string path)
|
||||||
@@ -940,15 +1048,15 @@ public class MersenneDivisorCycles
|
|||||||
|
|
||||||
if (PrimeTester.IsPrimeInternal(divisor, CancellationToken.None))
|
if (PrimeTester.IsPrimeInternal(divisor, CancellationToken.None))
|
||||||
{
|
{
|
||||||
PrimeOrderCalculator.PrimeOrderResult orderResult = PrimeOrderCalculator.Calculate(
|
ulong computedOrder = PrimeOrderCalculator.Calculate(
|
||||||
divisor,
|
divisor,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
divisorData,
|
divisorData,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
||||||
if (orderResult.Order != 0UL)
|
if (computedOrder != 0UL)
|
||||||
{
|
{
|
||||||
cycleLength = orderResult.Order;
|
cycleLength = computedOrder;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
using System;
|
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Open.Numeric.Primes;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
namespace PerfectNumbers.Core;
|
||||||
@@ -26,8 +22,6 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = threadCount;
|
|
||||||
|
|
||||||
candidates.Sort();
|
candidates.Sort();
|
||||||
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
|
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
|
||||||
int candidateCount = candidateSpan.Length;
|
int candidateCount = candidateSpan.Length;
|
||||||
@@ -35,12 +29,12 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
bool applyStartPrime = startPrime > 0UL;
|
bool applyStartPrime = startPrime > 0UL;
|
||||||
int skippedByPreviousResults = 0;
|
int skippedByPreviousResults = 0;
|
||||||
|
|
||||||
|
ArrayPool<ulong> pool = ThreadStaticPools.UlongPool;
|
||||||
if (previousResults is not null && previousResults.Count > 0)
|
if (previousResults is not null && previousResults.Count > 0)
|
||||||
{
|
{
|
||||||
int recordedCount = previousResults.Count;
|
int recordedCount = previousResults.Count;
|
||||||
ulong[] recordedCandidates = ArrayPool<ulong>.Shared.Rent(recordedCount);
|
ulong[] recordedCandidates = pool.Rent(recordedCount);
|
||||||
try
|
|
||||||
{
|
|
||||||
Span<ulong> recordedSpan = recordedCandidates.AsSpan(0, recordedCount);
|
Span<ulong> recordedSpan = recordedCandidates.AsSpan(0, recordedCount);
|
||||||
int recordedIndex = 0;
|
int recordedIndex = 0;
|
||||||
foreach (ulong value in previousResults.Keys)
|
foreach (ulong value in previousResults.Keys)
|
||||||
@@ -78,11 +72,8 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
candidateSpan = CollectionsMarshal.AsSpan(candidates);
|
candidateSpan = CollectionsMarshal.AsSpan(candidates);
|
||||||
candidateCount = candidateSpan.Length;
|
candidateCount = candidateSpan.Length;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
finally
|
pool.Return(recordedCandidates, clearArray: true);
|
||||||
{
|
|
||||||
ArrayPool<ulong>.Shared.Return(recordedCandidates, clearArray: true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skippedByPreviousResults > 0)
|
if (skippedByPreviousResults > 0)
|
||||||
@@ -112,9 +103,12 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
ulong maxPrime = 0UL;
|
ulong maxPrime = 0UL;
|
||||||
int primeWriteIndex = 0;
|
int primeWriteIndex = 0;
|
||||||
|
|
||||||
using IEnumerator<ulong> primeEnumerator = Prime.Numbers.GetEnumerator();
|
// This implementation is terribly slow, while this method expect prime p given as --filter-p input already.
|
||||||
bool hasPrime = primeEnumerator.MoveNext();
|
// We don't need to additionally check it.
|
||||||
ulong currentPrime = hasPrime ? primeEnumerator.Current : 0UL;
|
|
||||||
|
// using IEnumerator<ulong> primeEnumerator = Prime.Numbers.GetEnumerator();
|
||||||
|
// bool hasPrime = primeEnumerator.MoveNext();
|
||||||
|
// ulong currentPrime = hasPrime ? primeEnumerator.Current : 0UL;
|
||||||
|
|
||||||
for (int index = startIndex; index < candidateCount; index++)
|
for (int index = startIndex; index < candidateCount; index++)
|
||||||
{
|
{
|
||||||
@@ -127,23 +121,27 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (hasPrime && currentPrime < candidate)
|
// This implementation is terribly slow, while this method expect prime p given as --filter-p input already.
|
||||||
{
|
// We don't need to additionally check it.
|
||||||
hasPrime = primeEnumerator.MoveNext();
|
|
||||||
if (!hasPrime)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPrime = primeEnumerator.Current;
|
// while (hasPrime && currentPrime < candidate)
|
||||||
}
|
// {
|
||||||
|
// hasPrime = primeEnumerator.MoveNext();
|
||||||
|
// if (!hasPrime)
|
||||||
|
// {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
if (!hasPrime || currentPrime != candidate)
|
// currentPrime = primeEnumerator.Current;
|
||||||
{
|
// }
|
||||||
markComposite();
|
|
||||||
printResult(candidate, false, false, false);
|
// if (!hasPrime || currentPrime != candidate)
|
||||||
continue;
|
// if (!Number.IsPrime(candidate))
|
||||||
}
|
// {
|
||||||
|
// markComposite();
|
||||||
|
// printResult(candidate, false, false, false);
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
primesSpan[primeWriteIndex++] = candidate;
|
primesSpan[primeWriteIndex++] = candidate;
|
||||||
|
|
||||||
@@ -181,12 +179,10 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
tester.ConfigureFromMaxPrime(maxPrime);
|
tester.ConfigureFromMaxPrime(maxPrime);
|
||||||
|
|
||||||
int primeCount = primesToTest.Count;
|
int primeCount = primesToTest.Count;
|
||||||
ulong[] primeBatch = ArrayPool<ulong>.Shared.Rent(primeCount);
|
ulong[] primeBatch = pool.Rent(primeCount);
|
||||||
ulong[] allowedMaxBatch = ArrayPool<ulong>.Shared.Rent(primeCount);
|
ulong[] allowedMaxBatch = pool.Rent(primeCount);
|
||||||
List<ulong> filteredPrimes = new(primeCount);
|
List<ulong> filteredPrimes = new(primeCount);
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Span<ulong> primeSpan = primeBatch.AsSpan(0, primeCount);
|
Span<ulong> primeSpan = primeBatch.AsSpan(0, primeCount);
|
||||||
Span<ulong> allowedMaxSpan = allowedMaxBatch.AsSpan(0, primeCount);
|
Span<ulong> allowedMaxSpan = allowedMaxBatch.AsSpan(0, primeCount);
|
||||||
|
|
||||||
@@ -211,12 +207,9 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
|
|
||||||
filteredPrimes.Add(prime);
|
filteredPrimes.Add(prime);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
finally
|
pool.Return(allowedMaxBatch, clearArray: true);
|
||||||
{
|
pool.Return(primeBatch, clearArray: true);
|
||||||
ArrayPool<ulong>.Shared.Return(allowedMaxBatch, clearArray: true);
|
|
||||||
ArrayPool<ulong>.Shared.Return(primeBatch, clearArray: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filteredPrimes.Count == 0)
|
if (filteredPrimes.Count == 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -371,10 +371,6 @@ public static class MersennePrimeFactorTester
|
|||||||
return 2UL;
|
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;
|
ulong c, d, x, y, diff;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -383,8 +379,8 @@ public static class MersennePrimeFactorTester
|
|||||||
return 0UL;
|
return 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
c = (ulong)rng.Next(2, int.MaxValue);
|
c = (DeterministicRandom.NextUInt64() % (n - 1UL)) + 1UL;
|
||||||
x = (ulong)rng.Next(2, int.MaxValue);
|
x = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL;
|
||||||
y = x;
|
y = x;
|
||||||
d = 1UL;
|
d = 1UL;
|
||||||
|
|
||||||
@@ -523,9 +519,6 @@ public static class MersennePrimeFactorTester
|
|||||||
return 2;
|
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;
|
UInt128 c, d, x, y, diff;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -534,8 +527,8 @@ public static class MersennePrimeFactorTester
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
c = (UInt128)rng.NextInt64(2, long.MaxValue);
|
c = (DeterministicRandom.NextUInt128() % (n - 1)) + 1;
|
||||||
x = (UInt128)rng.NextInt64(2, long.MaxValue);
|
x = (DeterministicRandom.NextUInt128() % (n - 2)) + 2;
|
||||||
y = x;
|
y = x;
|
||||||
d = 1;
|
d = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
namespace PerfectNumbers.Core;
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
using System.Buffers;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core
|
||||||
|
{
|
||||||
|
public sealed class PartialFactorResult
|
||||||
|
{
|
||||||
|
[ThreadStatic]
|
||||||
|
private static PartialFactorResult? s_poolHead;
|
||||||
|
|
||||||
|
public FactorEntry[]? Factors;
|
||||||
|
private PartialFactorResult? _next;
|
||||||
|
public ulong Cofactor;
|
||||||
|
public bool FullyFactored;
|
||||||
|
public int Count;
|
||||||
|
|
||||||
|
private PartialFactorResult()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PartialFactorResult Rent(in FactorEntry[]? factors, ulong cofactor, bool fullyFactored, int count)
|
||||||
|
{
|
||||||
|
PartialFactorResult? instance = s_poolHead;
|
||||||
|
if (instance is null)
|
||||||
|
{
|
||||||
|
instance = new PartialFactorResult();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s_poolHead = instance._next;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance._next = null;
|
||||||
|
instance.Factors = factors;
|
||||||
|
instance.Cofactor = cofactor;
|
||||||
|
instance.FullyFactored = fullyFactored;
|
||||||
|
instance.Count = count;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly PartialFactorResult Empty = new()
|
||||||
|
{
|
||||||
|
Cofactor = 1UL,
|
||||||
|
FullyFactored = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
public PartialFactorResult WithAdditionalPrime(ulong prime)
|
||||||
|
{
|
||||||
|
FactorEntry[]? source = Factors;
|
||||||
|
if (source is null || Count == 0)
|
||||||
|
{
|
||||||
|
FactorEntry[] local = ArrayPool<FactorEntry>.Shared.Rent(1);
|
||||||
|
local[0] = new FactorEntry(prime, 1);
|
||||||
|
return Rent(local, 1UL, true, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
FactorEntry[] extended = ArrayPool<FactorEntry>.Shared.Rent(Count + 1);
|
||||||
|
Array.Copy(source, 0, extended, 0, Count);
|
||||||
|
extended[Count] = new FactorEntry(prime, 1);
|
||||||
|
Span<FactorEntry> span = extended.AsSpan(0, Count + 1);
|
||||||
|
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
||||||
|
return Rent(extended, 1UL, true, Count + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (this != Empty)
|
||||||
|
{
|
||||||
|
FactorEntry[]? factors = Factors;
|
||||||
|
if (factors is not null)
|
||||||
|
{
|
||||||
|
Factors = null;
|
||||||
|
ThreadStaticPools.FactorEntryPool.Return(factors, clearArray: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
_next = s_poolHead;
|
||||||
|
s_poolHead = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ public static class PerfectNumberConstants
|
|||||||
public const int MaxQForDivisorCycles = 4_000_000;
|
public const int MaxQForDivisorCycles = 4_000_000;
|
||||||
public static readonly uint PrimesLimit = 1_000_000; //(ulong)Array.MaxLength;// 1_000_000;
|
public static readonly uint PrimesLimit = 1_000_000; //(ulong)Array.MaxLength;// 1_000_000;
|
||||||
public const int MaxOddPowersCount = 128;
|
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
|
// 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.
|
// divisor-cycle datasets we generate offline.
|
||||||
// TODO: Promote these magic numbers into a runtime profile derived from EvenPerfectBitScanner.Benchmarks so we can retune
|
// TODO: Promote these magic numbers into a runtime profile derived from EvenPerfectBitScanner.Benchmarks so we can retune
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,45 @@
|
|||||||
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
|
internal static partial class PrimeOrderCalculator
|
||||||
|
{
|
||||||
|
private static bool IsGpuPow2Allowed => s_pow2ModeInitialized && s_allowGpuPow2;
|
||||||
|
|
||||||
|
private const int GpuSmallPrimeFactorSlots = 64;
|
||||||
|
|
||||||
|
private static bool TryPopulateSmallPrimeFactorsGpu(ulong value, uint limit, Dictionary<ulong, int> counts, out ulong remaining)
|
||||||
|
{
|
||||||
|
ulong[] primeBufferArray = ThreadStaticPools.UlongPool.Rent(GpuSmallPrimeFactorSlots);
|
||||||
|
int[] exponentBufferArray = ThreadStaticPools.IntPool.Rent(GpuSmallPrimeFactorSlots);
|
||||||
|
Span<ulong> primeBuffer = primeBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots);
|
||||||
|
Span<int> exponentBuffer = exponentBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots);
|
||||||
|
primeBuffer.Clear();
|
||||||
|
exponentBuffer.Clear();
|
||||||
|
|
||||||
|
if (!PrimeOrderGpuHeuristics.TryPartialFactor(value, limit, primeBuffer, exponentBuffer, out int factorCount, out ulong gpuRemaining, out _))
|
||||||
|
{
|
||||||
|
remaining = value;
|
||||||
|
ThreadStaticPools.UlongPool.Return(primeBufferArray);
|
||||||
|
ThreadStaticPools.IntPool.Return(exponentBufferArray);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining = gpuRemaining;
|
||||||
|
for (int i = 0; i < factorCount; i++)
|
||||||
|
{
|
||||||
|
ulong primeValue = primeBuffer[i];
|
||||||
|
int exponent = exponentBuffer[i];
|
||||||
|
if (primeValue == 0UL || exponent == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
counts[primeValue] = exponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadStaticPools.UlongPool.Return(primeBufferArray);
|
||||||
|
ThreadStaticPools.IntPool.Return(exponentBufferArray);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
+152
-156
@@ -1,101 +1,127 @@
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Threading;
|
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
internal static partial class PrimeOrderCalculator
|
internal static partial class PrimeOrderCalculator
|
||||||
{
|
{
|
||||||
internal readonly struct PrimeOrderResultWide
|
[ThreadStatic]
|
||||||
|
private static bool s_pow2ModeInitialized;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static bool s_allowGpuPow2;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static PrimeOrderHeuristicDevice s_deviceMode;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static bool s_debugLoggingEnabled;
|
||||||
|
|
||||||
|
private static bool IsGpuHeuristicDevice => s_pow2ModeInitialized && s_deviceMode == PrimeOrderHeuristicDevice.Gpu;
|
||||||
|
|
||||||
|
private readonly struct Pow2ModeScope
|
||||||
{
|
{
|
||||||
public PrimeOrderResultWide(PrimeOrderStatus status, UInt128 order)
|
private readonly bool _previousInitialized;
|
||||||
|
private readonly bool _previousAllow;
|
||||||
|
private readonly PrimeOrderHeuristicDevice _previousDevice;
|
||||||
|
|
||||||
|
public Pow2ModeScope(PrimeOrderHeuristicDevice device)
|
||||||
{
|
{
|
||||||
Status = status;
|
_previousInitialized = s_pow2ModeInitialized;
|
||||||
Order = order;
|
_previousAllow = s_allowGpuPow2;
|
||||||
|
_previousDevice = s_deviceMode;
|
||||||
|
s_pow2ModeInitialized = true;
|
||||||
|
s_allowGpuPow2 = device == PrimeOrderHeuristicDevice.Gpu;
|
||||||
|
s_deviceMode = device;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PrimeOrderStatus Status { get; }
|
public void Dispose()
|
||||||
|
|
||||||
public UInt128 Order { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PrimeOrderResultWide Calculate(UInt128 prime, UInt128? previousOrder, PrimeOrderSearchConfig config)
|
|
||||||
=> Calculate(prime, previousOrder, config, PrimeOrderHeuristicDevice.Gpu);
|
|
||||||
|
|
||||||
public static PrimeOrderResultWide Calculate(
|
|
||||||
UInt128 prime,
|
|
||||||
UInt128? previousOrder,
|
|
||||||
PrimeOrderSearchConfig config,
|
|
||||||
PrimeOrderHeuristicDevice device)
|
|
||||||
{
|
{
|
||||||
using var scope = UsePow2Mode(device);
|
s_allowGpuPow2 = _previousAllow;
|
||||||
MontgomeryDivisorData divisorData;
|
s_pow2ModeInitialized = _previousInitialized;
|
||||||
if (prime <= ulong.MaxValue)
|
s_deviceMode = _previousDevice;
|
||||||
{
|
|
||||||
ulong? previous = null;
|
|
||||||
if (previousOrder.HasValue)
|
|
||||||
{
|
|
||||||
UInt128 previousValue = previousOrder.Value;
|
|
||||||
if (previousValue <= ulong.MaxValue)
|
|
||||||
{
|
|
||||||
previous = (ulong)previousValue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
previous = ulong.MaxValue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong prime64 = (ulong)prime;
|
private readonly struct DebugLoggingScope
|
||||||
divisorData = MontgomeryDivisorData.FromModulus(prime64);
|
|
||||||
PrimeOrderResult result = Calculate(prime64, previous, divisorData, config, device);
|
|
||||||
return new PrimeOrderResultWide(result.Status, result.Order);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
divisorData = default;
|
private readonly bool _previous;
|
||||||
|
|
||||||
|
public DebugLoggingScope(bool enabled)
|
||||||
|
{
|
||||||
|
_previous = s_debugLoggingEnabled;
|
||||||
|
s_debugLoggingEnabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CalculateWideInternal(prime, previousOrder, divisorData, config);
|
public void Dispose()
|
||||||
|
{
|
||||||
|
s_debugLoggingEnabled = _previous;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PrimeOrderResultWide CalculateWideInternal(UInt128 prime, UInt128? previousOrder, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config)
|
private static Pow2ModeScope UsePow2Mode(PrimeOrderHeuristicDevice device) => new(device);
|
||||||
|
|
||||||
|
private static DebugLoggingScope UseDebugLogging(bool enabled) => new(enabled);
|
||||||
|
|
||||||
|
[Conditional("DEBUG")]
|
||||||
|
private static void DebugLog(string message)
|
||||||
|
{
|
||||||
|
if (s_debugLoggingEnabled)
|
||||||
|
{
|
||||||
|
Console.WriteLine(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Conditional("DEBUG")]
|
||||||
|
private static void DebugLog(Func<string> messageFactory)
|
||||||
|
{
|
||||||
|
if (s_debugLoggingEnabled)
|
||||||
|
{
|
||||||
|
Console.WriteLine(messageFactory());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static UInt128 CalculateWideInternal(in UInt128 prime, in UInt128? previousOrder, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
|
||||||
{
|
{
|
||||||
if (prime <= UInt128.One)
|
if (prime <= UInt128.One)
|
||||||
{
|
{
|
||||||
return new PrimeOrderResultWide(PrimeOrderStatus.Found, UInt128.One);
|
return UInt128.One;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prime == (UInt128)3UL)
|
if (prime == (UInt128)3UL)
|
||||||
{
|
{
|
||||||
return new PrimeOrderResultWide(PrimeOrderStatus.Found, (UInt128)2UL);
|
return (UInt128)2UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt128 phi = prime - UInt128.One;
|
UInt128 phi = prime - UInt128.One;
|
||||||
using var debugScope = UseDebugLogging(!IsGpuHeuristicDevice);
|
var debugScope = UseDebugLogging(!IsGpuHeuristicDevice);
|
||||||
PartialFactorResult128 phiFactors = PartialFactorWide(phi, config);
|
PartialFactorResult128 phiFactors = PartialFactorWide(phi, config);
|
||||||
|
UInt128 result;
|
||||||
if (phiFactors.Factors is null)
|
if (phiFactors.Factors is null)
|
||||||
{
|
{
|
||||||
return FinishStrictlyWide(prime, divisorData, config.Mode);
|
result = FinishStrictlyWide(prime, divisorData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = RunHeuristicPipelineWide(prime, previousOrder, divisorData, config, phi, phiFactors);
|
||||||
}
|
}
|
||||||
|
|
||||||
return RunHeuristicPipelineWide(prime, previousOrder, divisorData, config, phi, phiFactors);
|
debugScope.Dispose();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PrimeOrderResultWide RunHeuristicPipelineWide(
|
private static UInt128 RunHeuristicPipelineWide(
|
||||||
UInt128 prime,
|
in UInt128 prime,
|
||||||
UInt128? previousOrder,
|
in UInt128? previousOrder,
|
||||||
in MontgomeryDivisorData divisorData,
|
in MontgomeryDivisorData divisorData,
|
||||||
PrimeOrderSearchConfig config,
|
in PrimeOrderSearchConfig config,
|
||||||
UInt128 phi,
|
in UInt128 phi,
|
||||||
PartialFactorResult128 phiFactors)
|
in PartialFactorResult128 phiFactors)
|
||||||
{
|
{
|
||||||
if (phiFactors.FullyFactored && TrySpecialMaxWide(phi, prime, divisorData, phiFactors))
|
if (phiFactors.FullyFactored && TrySpecialMaxWide(phi, prime, divisorData, phiFactors))
|
||||||
{
|
{
|
||||||
return new PrimeOrderResultWide(PrimeOrderStatus.Found, phi);
|
return phi;
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt128 candidateOrder = InitializeStartingOrderWide(prime, phi, divisorData);
|
UInt128 candidateOrder = InitializeStartingOrderWide(prime, phi, divisorData);
|
||||||
@@ -103,30 +129,28 @@ internal static partial class PrimeOrderCalculator
|
|||||||
|
|
||||||
if (TryConfirmOrderWide(prime, candidateOrder, divisorData, config))
|
if (TryConfirmOrderWide(prime, candidateOrder, divisorData, config))
|
||||||
{
|
{
|
||||||
return new PrimeOrderResultWide(PrimeOrderStatus.Found, candidateOrder);
|
return candidateOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.Mode == PrimeOrderMode.Strict)
|
if (config.Mode == PrimeOrderMode.Strict)
|
||||||
{
|
{
|
||||||
return FinishStrictlyWide(prime, divisorData, PrimeOrderMode.Strict);
|
return FinishStrictlyWide(prime, divisorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryHeuristicFinishWide(prime, candidateOrder, previousOrder, divisorData, 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, divisorData, config.Mode);
|
return candidateOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PrimeOrderResultWide FinishStrictlyWide(UInt128 prime, in MontgomeryDivisorData divisorData, PrimeOrderMode mode)
|
private static UInt128 FinishStrictlyWide(in UInt128 prime, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
UInt128 strictOrder = CalculateByFactorizationWide(prime, divisorData);
|
return CalculateByFactorizationWide(prime, divisorData);
|
||||||
PrimeOrderStatus status = mode == PrimeOrderMode.Strict ? PrimeOrderStatus.Found : PrimeOrderStatus.HeuristicUnresolved;
|
|
||||||
return new PrimeOrderResultWide(status, strictOrder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TrySpecialMaxWide(UInt128 phi, UInt128 prime, in MontgomeryDivisorData divisorData, PartialFactorResult128 factors)
|
private static bool TrySpecialMaxWide(in UInt128 phi, in UInt128 prime, in MontgomeryDivisorData divisorData, in PartialFactorResult128 factors)
|
||||||
{
|
{
|
||||||
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
|
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
|
||||||
int length = factors.Count;
|
int length = factors.Count;
|
||||||
@@ -143,7 +167,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UInt128 InitializeStartingOrderWide(UInt128 prime, UInt128 phi, in MontgomeryDivisorData divisorData)
|
private static UInt128 InitializeStartingOrderWide(in UInt128 prime, in UInt128 phi, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
UInt128 order = phi;
|
UInt128 order = phi;
|
||||||
UInt128 mod8 = prime & (UInt128)7UL;
|
UInt128 mod8 = prime & (UInt128)7UL;
|
||||||
@@ -159,19 +183,20 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UInt128 ExponentLoweringWide(UInt128 order, UInt128 prime, in MontgomeryDivisorData divisorData, PartialFactorResult128 factors)
|
private static UInt128 ExponentLoweringWide(UInt128 order, in UInt128 prime, in MontgomeryDivisorData divisorData, in PartialFactorResult128 factors)
|
||||||
{
|
|
||||||
FactorEntry128[]? tempArray = null;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
|
ArrayPool<FactorEntry128> pool = ThreadStaticPools.FactorEntry128Pool;
|
||||||
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
|
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
|
||||||
int length = factors.Count;
|
int length = factors.Count;
|
||||||
if (length == 0)
|
// TODO: This should never trigger from production code - check
|
||||||
|
// if (length == 0)
|
||||||
|
// {
|
||||||
|
// return order;
|
||||||
|
// }
|
||||||
|
FactorEntry128[]? tempArray = pool.Rent(length + 1);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return order;
|
|
||||||
}
|
|
||||||
|
|
||||||
tempArray = ArrayPool<FactorEntry128>.Shared.Rent(length + 1);
|
|
||||||
Span<FactorEntry128> buffer = tempArray.AsSpan(0, length);
|
Span<FactorEntry128> buffer = tempArray.AsSpan(0, length);
|
||||||
factorSpan.CopyTo(buffer);
|
factorSpan.CopyTo(buffer);
|
||||||
|
|
||||||
@@ -207,14 +232,11 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (tempArray is not null)
|
pool.Return(tempArray, clearArray: false);
|
||||||
{
|
|
||||||
ArrayPool<FactorEntry128>.Shared.Return(tempArray, clearArray: false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryConfirmOrderWide(UInt128 prime, UInt128 order, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config)
|
private static bool TryConfirmOrderWide(in UInt128 prime, in UInt128 order, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
|
||||||
{
|
{
|
||||||
if (order == UInt128.Zero)
|
if (order == UInt128.Zero)
|
||||||
{
|
{
|
||||||
@@ -272,11 +294,11 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryHeuristicFinishWide(
|
private static bool TryHeuristicFinishWide(
|
||||||
UInt128 prime,
|
in UInt128 prime,
|
||||||
UInt128 order,
|
in UInt128 order,
|
||||||
UInt128? previousOrder,
|
in UInt128? previousOrder,
|
||||||
in MontgomeryDivisorData divisorData,
|
in MontgomeryDivisorData divisorData,
|
||||||
PrimeOrderSearchConfig config,
|
in PrimeOrderSearchConfig config,
|
||||||
out UInt128 result)
|
out UInt128 result)
|
||||||
{
|
{
|
||||||
result = UInt128.Zero;
|
result = UInt128.Zero;
|
||||||
@@ -348,7 +370,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SortCandidatesWide(UInt128 prime, UInt128? previousOrder, List<UInt128> candidates)
|
private static void SortCandidatesWide(UInt128 prime, in UInt128? previousOrder, List<UInt128> candidates)
|
||||||
{
|
{
|
||||||
UInt128 previous = previousOrder ?? UInt128.Zero;
|
UInt128 previous = previousOrder ?? UInt128.Zero;
|
||||||
int previousGroup = previousOrder.HasValue ? GetGroupWide(previousOrder.Value, prime) : 1;
|
int previousGroup = previousOrder.HasValue ? GetGroupWide(previousOrder.Value, prime) : 1;
|
||||||
@@ -376,9 +398,9 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static CandidateKey128 BuildKeyWide(
|
private static CandidateKey128 BuildKeyWide(
|
||||||
UInt128 value,
|
in UInt128 value,
|
||||||
UInt128 prime,
|
in UInt128 prime,
|
||||||
UInt128 previous,
|
in UInt128 previous,
|
||||||
int previousGroup,
|
int previousGroup,
|
||||||
bool hasPrevious)
|
bool hasPrevious)
|
||||||
{
|
{
|
||||||
@@ -400,7 +422,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return new CandidateKey128(primary, false, distance, false, value);
|
return new CandidateKey128(primary, false, distance, false, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int CompareComponents(bool descendingX, UInt128 valueX, bool descendingY, UInt128 valueY)
|
private static int CompareComponents(bool descendingX, in UInt128 valueX, bool descendingY, in UInt128 valueY)
|
||||||
{
|
{
|
||||||
if (descendingX == descendingY)
|
if (descendingX == descendingY)
|
||||||
{
|
{
|
||||||
@@ -425,7 +447,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return valueX > valueY ? 1 : -1;
|
return valueX > valueY ? 1 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetGroupWide(UInt128 value, UInt128 prime)
|
private static int GetGroupWide(in UInt128 value, in UInt128 prime)
|
||||||
{
|
{
|
||||||
UInt128 threshold1 = prime >> 3;
|
UInt128 threshold1 = prime >> 3;
|
||||||
if (value <= threshold1)
|
if (value <= threshold1)
|
||||||
@@ -448,7 +470,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void BuildCandidatesWide(UInt128 order, FactorEntry128[] factors, int count, List<UInt128> candidates, int limit)
|
private static void BuildCandidatesWide(in UInt128 order, FactorEntry128[] factors, int count, List<UInt128> candidates, int limit)
|
||||||
{
|
{
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
{
|
{
|
||||||
@@ -461,10 +483,10 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void BuildCandidatesRecursiveWide(
|
private static void BuildCandidatesRecursiveWide(
|
||||||
UInt128 order,
|
in UInt128 order,
|
||||||
ReadOnlySpan<FactorEntry128> factors,
|
in ReadOnlySpan<FactorEntry128> factors,
|
||||||
int index,
|
int index,
|
||||||
UInt128 divisorProduct,
|
in UInt128 divisorProduct,
|
||||||
List<UInt128> candidates,
|
List<UInt128> candidates,
|
||||||
int limit)
|
int limit)
|
||||||
{
|
{
|
||||||
@@ -520,7 +542,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryConfirmCandidateWide(UInt128 prime, UInt128 candidate, PrimeOrderSearchConfig config, ref int powUsed, int powBudget, in MontgomeryDivisorData divisorData)
|
private static bool TryConfirmCandidateWide(in UInt128 prime, in UInt128 candidate, in PrimeOrderSearchConfig config, ref int powUsed, int powBudget, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
PartialFactorResult128 factorization = PartialFactorWide(candidate, config);
|
PartialFactorResult128 factorization = PartialFactorWide(candidate, config);
|
||||||
if (factorization.Factors is null)
|
if (factorization.Factors is null)
|
||||||
@@ -573,7 +595,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PartialFactorResult128 PartialFactorWide(UInt128 value, PrimeOrderSearchConfig config)
|
private static PartialFactorResult128 PartialFactorWide(in UInt128 value, in PrimeOrderSearchConfig config)
|
||||||
{
|
{
|
||||||
if (value <= UInt128.One)
|
if (value <= UInt128.One)
|
||||||
{
|
{
|
||||||
@@ -614,12 +636,12 @@ internal static partial class PrimeOrderCalculator
|
|||||||
|
|
||||||
if (config.PollardRhoMilliseconds > 0 && pending.Count > 0)
|
if (config.PollardRhoMilliseconds > 0 && pending.Count > 0)
|
||||||
{
|
{
|
||||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
long deadlineTimestamp = CreateDeadlineTimestamp(config.PollardRhoMilliseconds);
|
||||||
long budgetTicks = TimeSpan.FromMilliseconds(config.PollardRhoMilliseconds).Ticks;
|
|
||||||
Stack<UInt128> stack = new();
|
Stack<UInt128> stack = new();
|
||||||
stack.Push(remaining);
|
stack.Push(remaining);
|
||||||
pending.Clear();
|
pending.Clear();
|
||||||
|
|
||||||
|
long timestamp = 0L; // reused for deadline checks.
|
||||||
while (stack.Count > 0)
|
while (stack.Count > 0)
|
||||||
{
|
{
|
||||||
UInt128 composite = stack.Pop();
|
UInt128 composite = stack.Pop();
|
||||||
@@ -634,13 +656,14 @@ internal static partial class PrimeOrderCalculator
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stopwatch.ElapsedTicks > budgetTicks)
|
timestamp = Stopwatch.GetTimestamp();
|
||||||
|
if (timestamp > deadlineTimestamp)
|
||||||
{
|
{
|
||||||
pending.Add(composite);
|
pending.Add(composite);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryPollardRhoWide(composite, stopwatch, budgetTicks, out UInt128 factor))
|
if (!TryPollardRhoWide(composite, deadlineTimestamp, out UInt128 factor))
|
||||||
{
|
{
|
||||||
pending.Add(composite);
|
pending.Add(composite);
|
||||||
continue;
|
continue;
|
||||||
@@ -673,7 +696,8 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return new PartialFactorResult128(null, value, false, 0);
|
return new PartialFactorResult128(null, value, false, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
FactorEntry128[]? rented = counts.Count > 0 ? ArrayPool<FactorEntry128>.Shared.Rent(counts.Count) : null;
|
ArrayPool<FactorEntry128>? pool = null;
|
||||||
|
FactorEntry128[]? rented = counts.Count > 0 ? (pool = ThreadStaticPools.FactorEntry128Pool).Rent(counts.Count) : null;
|
||||||
int index = 0;
|
int index = 0;
|
||||||
if (rented is not null)
|
if (rented is not null)
|
||||||
{
|
{
|
||||||
@@ -685,20 +709,20 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
|
|
||||||
FactorEntry128[]? resultArray = null;
|
FactorEntry128[]? resultArray = null;
|
||||||
if (rented is not null)
|
if (pool is not null)
|
||||||
{
|
{
|
||||||
Span<FactorEntry128> span = rented.AsSpan(0, index);
|
Span<FactorEntry128> span = rented.AsSpan(0, index);
|
||||||
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
||||||
resultArray = new FactorEntry128[index];
|
resultArray = new FactorEntry128[index];
|
||||||
span.CopyTo(resultArray);
|
span.CopyTo(resultArray);
|
||||||
ArrayPool<FactorEntry128>.Shared.Return(rented, clearArray: false);
|
pool.Return(rented!, clearArray: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fullyFactored = cofactor == UInt128.One;
|
bool fullyFactored = cofactor == UInt128.One;
|
||||||
return new PartialFactorResult128(resultArray, cofactor, fullyFactored, index);
|
return new PartialFactorResult128(resultArray, cofactor, fullyFactored, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UInt128 PopulateSmallPrimeFactorsCpuWide(UInt128 value, uint limit, Dictionary<UInt128, int> counts)
|
private static UInt128 PopulateSmallPrimeFactorsCpuWide(in UInt128 value, uint limit, Dictionary<UInt128, int> counts)
|
||||||
{
|
{
|
||||||
UInt128 remaining = value;
|
UInt128 remaining = value;
|
||||||
uint[] primes = PrimesGenerator.SmallPrimes;
|
uint[] primes = PrimesGenerator.SmallPrimes;
|
||||||
@@ -738,7 +762,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
|
|
||||||
return remaining;
|
return remaining;
|
||||||
}
|
}
|
||||||
private static bool TryPollardRhoWide(UInt128 n, Stopwatch stopwatch, long budgetTicks, out UInt128 factor)
|
private static bool TryPollardRhoWide(in UInt128 n, long deadlineTimestamp, out UInt128 factor)
|
||||||
{
|
{
|
||||||
factor = UInt128.Zero;
|
factor = UInt128.Zero;
|
||||||
if ((n & UInt128.One) == UInt128.Zero)
|
if ((n & UInt128.One) == UInt128.Zero)
|
||||||
@@ -750,10 +774,12 @@ internal static partial class PrimeOrderCalculator
|
|||||||
UInt128 c = UInt128.One;
|
UInt128 c = UInt128.One;
|
||||||
UInt128 x = (UInt128)2UL;
|
UInt128 x = (UInt128)2UL;
|
||||||
UInt128 y = x;
|
UInt128 y = x;
|
||||||
|
long timestamp = 0L; // reused for deadline checks.
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (stopwatch.ElapsedTicks > budgetTicks)
|
timestamp = Stopwatch.GetTimestamp();
|
||||||
|
if (timestamp > deadlineTimestamp)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -780,7 +806,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UInt128 AdvancePolynomialWide(UInt128 x, UInt128 c, UInt128 modulus)
|
private static UInt128 AdvancePolynomialWide(in UInt128 x, in UInt128 c, in UInt128 modulus)
|
||||||
{
|
{
|
||||||
BigInteger value = (BigInteger)x;
|
BigInteger value = (BigInteger)x;
|
||||||
value = (value * value + (BigInteger)c) % (BigInteger)modulus;
|
value = (value * value + (BigInteger)c) % (BigInteger)modulus;
|
||||||
@@ -818,7 +844,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int TrailingZeroCountWide(UInt128 value)
|
private static int TrailingZeroCountWide(in UInt128 value)
|
||||||
{
|
{
|
||||||
if (value == UInt128.Zero)
|
if (value == UInt128.Zero)
|
||||||
{
|
{
|
||||||
@@ -874,7 +900,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void FactorCompletelyWide(UInt128 value, Dictionary<UInt128, int> counts)
|
private static void FactorCompletelyWide(in UInt128 value, Dictionary<UInt128, int> counts)
|
||||||
{
|
{
|
||||||
if (value <= UInt128.One)
|
if (value <= UInt128.One)
|
||||||
{
|
{
|
||||||
@@ -892,7 +918,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
FactorCompletelyWide(value / factor, counts);
|
FactorCompletelyWide(value / factor, counts);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UInt128 PollardRhoWide(UInt128 n)
|
private static UInt128 PollardRhoWide(in UInt128 n)
|
||||||
{
|
{
|
||||||
if ((n & UInt128.One) == UInt128.Zero)
|
if ((n & UInt128.One) == UInt128.Zero)
|
||||||
{
|
{
|
||||||
@@ -925,7 +951,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsPrimeWide(UInt128 value)
|
private static bool IsPrimeWide(in UInt128 value)
|
||||||
{
|
{
|
||||||
if (value <= ulong.MaxValue)
|
if (value <= ulong.MaxValue)
|
||||||
{
|
{
|
||||||
@@ -1005,7 +1031,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UInt128 Pow2ModWide(UInt128 exponent, in UInt128 modulus, in MontgomeryDivisorData divisorData)
|
private static UInt128 Pow2ModWide(in UInt128 exponent, in UInt128 modulus, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
if (modulus == UInt128.One)
|
if (modulus == UInt128.One)
|
||||||
{
|
{
|
||||||
@@ -1035,7 +1061,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return exponent.Pow2MontgomeryModWindowed(modulus);
|
return exponent.Pow2MontgomeryModWindowed(modulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddFactor(Dictionary<UInt128, int> counts, UInt128 prime, int exponent)
|
private static void AddFactor(Dictionary<UInt128, int> counts, in UInt128 prime, int exponent)
|
||||||
{
|
{
|
||||||
if (counts.TryGetValue(prime, out int existing))
|
if (counts.TryGetValue(prime, out int existing))
|
||||||
{
|
{
|
||||||
@@ -1047,58 +1073,28 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly struct CandidateKey128
|
private readonly struct CandidateKey128(int primary, bool secondaryDescending, in UInt128 secondary, bool tertiaryDescending, in UInt128 tertiary)
|
||||||
{
|
{
|
||||||
public CandidateKey128(int primary, bool secondaryDescending, UInt128 secondary, bool tertiaryDescending, UInt128 tertiary)
|
public int Primary { get; } = primary;
|
||||||
{
|
|
||||||
Primary = primary;
|
public bool SecondaryDescending { get; } = secondaryDescending;
|
||||||
SecondaryDescending = secondaryDescending;
|
|
||||||
Secondary = secondary;
|
public UInt128 Secondary { get; } = secondary;
|
||||||
TertiaryDescending = tertiaryDescending;
|
|
||||||
Tertiary = tertiary;
|
public bool TertiaryDescending { get; } = tertiaryDescending;
|
||||||
|
|
||||||
|
public UInt128 Tertiary { get; } = tertiary;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Primary { get; }
|
private readonly struct PartialFactorResult128(FactorEntry128[]? factors, UInt128 cofactor, bool fullyFactored, int count)
|
||||||
|
|
||||||
public bool SecondaryDescending { get; }
|
|
||||||
|
|
||||||
public UInt128 Secondary { get; }
|
|
||||||
|
|
||||||
public bool TertiaryDescending { get; }
|
|
||||||
|
|
||||||
public UInt128 Tertiary { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly struct FactorEntry128
|
|
||||||
{
|
{
|
||||||
public FactorEntry128(UInt128 value, int exponent)
|
public FactorEntry128[]? Factors { get; } = factors;
|
||||||
{
|
|
||||||
Value = value;
|
|
||||||
Exponent = exponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UInt128 Value { get; }
|
public UInt128 Cofactor { get; } = cofactor;
|
||||||
|
|
||||||
public int Exponent { get; }
|
public bool FullyFactored { get; } = fullyFactored;
|
||||||
}
|
|
||||||
|
|
||||||
private readonly struct PartialFactorResult128
|
public int Count { get; } = count;
|
||||||
{
|
|
||||||
public PartialFactorResult128(FactorEntry128[]? factors, UInt128 cofactor, bool fullyFactored, int count)
|
|
||||||
{
|
|
||||||
Factors = factors;
|
|
||||||
Cofactor = cofactor;
|
|
||||||
FullyFactored = fullyFactored;
|
|
||||||
Count = count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FactorEntry128[]? Factors { get; }
|
|
||||||
|
|
||||||
public UInt128 Cofactor { get; }
|
|
||||||
|
|
||||||
public bool FullyFactored { get; }
|
|
||||||
|
|
||||||
public int Count { get; }
|
|
||||||
|
|
||||||
public static PartialFactorResult128 Empty => new(null, UInt128.One, true, 0);
|
public static PartialFactorResult128 Empty => new(null, UInt128.One, true, 0);
|
||||||
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,320 @@
|
|||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using PerfectNumbers.Core.Cpu;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core
|
||||||
|
{
|
||||||
|
public readonly struct ThreadStaticPools
|
||||||
|
{
|
||||||
|
[ThreadStatic]
|
||||||
|
private static ArrayPool<FactorEntry>? _factorEntryPool;
|
||||||
|
|
||||||
|
public static ArrayPool<FactorEntry> FactorEntryPool
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _factorEntryPool ??= ArrayPool<FactorEntry>.Create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static ArrayPool<FactorEntry128>? _factorEntry128Pool;
|
||||||
|
|
||||||
|
public static ArrayPool<FactorEntry128> FactorEntry128Pool
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _factorEntry128Pool ??= ArrayPool<FactorEntry128>.Create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static ArrayPool<KeyValuePair<ulong, int>>? _factorKeyValuePairPool;
|
||||||
|
|
||||||
|
public static ArrayPool<KeyValuePair<ulong, int>> FactorKeyValuePairPool
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _factorKeyValuePairPool ??= ArrayPool<KeyValuePair<ulong, int>>.Create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[ThreadStatic]
|
||||||
|
private static MersenneCpuDivisorScanSession? _mersenneCpuDivisorSession;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal static MersenneCpuDivisorScanSession? RentMersenneCpuDivisorSession()
|
||||||
|
{
|
||||||
|
MersenneCpuDivisorScanSession? session = _mersenneCpuDivisorSession;
|
||||||
|
if (session is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_mersenneCpuDivisorSession = null;
|
||||||
|
session.Reset();
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal static void ReturnMersenneCpuDivisorSession(MersenneCpuDivisorScanSession session)
|
||||||
|
{
|
||||||
|
_mersenneCpuDivisorSession = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static ArrayPool<ulong>? _ulongPool;
|
||||||
|
|
||||||
|
public static ArrayPool<ulong> UlongPool
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _ulongPool ??= ArrayPool<ulong>.Create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static ArrayPool<int>? _intPool;
|
||||||
|
|
||||||
|
public static ArrayPool<int> IntPool
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _intPool ??= ArrayPool<int>.Create();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<List<ulong>>? _ulongListPool;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static List<ulong> RentUlongList(int capacityHint)
|
||||||
|
{
|
||||||
|
List<List<ulong>>? pool = _ulongListPool;
|
||||||
|
if (pool is not null && pool.Count > 0)
|
||||||
|
{
|
||||||
|
int lastIndex = pool.Count - 1;
|
||||||
|
List<ulong> list = pool[lastIndex];
|
||||||
|
pool.RemoveAt(lastIndex);
|
||||||
|
if (list.Count > 0)
|
||||||
|
{
|
||||||
|
list.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.Capacity < capacityHint)
|
||||||
|
{
|
||||||
|
list.Capacity = capacityHint;
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new List<ulong>(capacityHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void ReturnUlongList(List<ulong> list)
|
||||||
|
{
|
||||||
|
List<List<ulong>>? pool = _ulongListPool;
|
||||||
|
if (pool is null)
|
||||||
|
{
|
||||||
|
pool = new List<List<ulong>>(4);
|
||||||
|
_ulongListPool = pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.Add(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<Stack<ulong>>? _ulongStackPool;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Stack<ulong> RentUlongStack(int capacityHint)
|
||||||
|
{
|
||||||
|
List<Stack<ulong>>? pool = _ulongStackPool;
|
||||||
|
if (pool is not null && pool.Count > 0)
|
||||||
|
{
|
||||||
|
int lastIndex = pool.Count - 1;
|
||||||
|
Stack<ulong> stack = pool[lastIndex];
|
||||||
|
pool.RemoveAt(lastIndex);
|
||||||
|
if (stack.Count > 0)
|
||||||
|
{
|
||||||
|
stack.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stack.Count < capacityHint)
|
||||||
|
{
|
||||||
|
stack.EnsureCapacity(capacityHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Stack<ulong>(capacityHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void ReturnUlongStack(Stack<ulong> stack)
|
||||||
|
{
|
||||||
|
List<Stack<ulong>>? pool = _ulongStackPool;
|
||||||
|
if (pool is null)
|
||||||
|
{
|
||||||
|
pool = new List<Stack<ulong>>(4);
|
||||||
|
_ulongStackPool = pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.Add(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<Dictionary<ulong, int>>? _ulongIntDictionaryPool;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Dictionary<ulong, int> RentUlongIntDictionary(int capacityHint)
|
||||||
|
{
|
||||||
|
List<Dictionary<ulong, int>>? pool = _ulongIntDictionaryPool;
|
||||||
|
if (pool is not null && pool.Count > 0)
|
||||||
|
{
|
||||||
|
int lastIndex = pool.Count - 1;
|
||||||
|
Dictionary<ulong, int> dictionary = pool[lastIndex];
|
||||||
|
pool.RemoveAt(lastIndex);
|
||||||
|
if (dictionary.Count > 0)
|
||||||
|
{
|
||||||
|
dictionary.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dictionary.Count < capacityHint)
|
||||||
|
{
|
||||||
|
dictionary.EnsureCapacity(capacityHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Dictionary<ulong, int>(capacityHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void ReturnUlongIntDictionary(Dictionary<ulong, int> dictionary)
|
||||||
|
{
|
||||||
|
List<Dictionary<ulong, int>>? pool = _ulongIntDictionaryPool;
|
||||||
|
if (pool is null)
|
||||||
|
{
|
||||||
|
pool = new List<Dictionary<ulong, int>>(4);
|
||||||
|
_ulongIntDictionaryPool = pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.Add(dictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? _mersenneFactorCacheDictionary;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry> RentMersenneFactorCacheDictionary()
|
||||||
|
{
|
||||||
|
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? cache = _mersenneFactorCacheDictionary;
|
||||||
|
if (cache is null)
|
||||||
|
{
|
||||||
|
return new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
_mersenneFactorCacheDictionary = null;
|
||||||
|
cache.Clear();
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void ReturnMersenneFactorCacheDictionary(Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry> cache)
|
||||||
|
{
|
||||||
|
for (Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>.Enumerator enumerator = cache.GetEnumerator(); enumerator.MoveNext();)
|
||||||
|
{
|
||||||
|
enumerator.Current.Value.ReturnToPool();
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.Clear();
|
||||||
|
_mersenneFactorCacheDictionary = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static MersenneDivisorCycles.FactorCacheEntry? _factorCacheEntryPoolHead;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static MersenneDivisorCycles.FactorCacheEntry RentFactorCacheEntry()
|
||||||
|
{
|
||||||
|
MersenneDivisorCycles.FactorCacheEntry? entry = _factorCacheEntryPoolHead;
|
||||||
|
if (entry is null)
|
||||||
|
{
|
||||||
|
entry = new MersenneDivisorCycles.FactorCacheEntry();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_factorCacheEntryPoolHead = entry.Next;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Reset();
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void ReturnFactorCacheEntry(MersenneDivisorCycles.FactorCacheEntry entry)
|
||||||
|
{
|
||||||
|
entry.Next = _factorCacheEntryPoolHead;
|
||||||
|
_factorCacheEntryPoolHead = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static Dictionary<ulong, int>? _factorCountDictionary;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Dictionary<ulong, int> RentFactorCountDictionary()
|
||||||
|
{
|
||||||
|
Dictionary<ulong, int>? dictionary = _factorCountDictionary;
|
||||||
|
if (dictionary is null)
|
||||||
|
{
|
||||||
|
return new Dictionary<ulong, int>(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
_factorCountDictionary = null;
|
||||||
|
dictionary.Clear();
|
||||||
|
return dictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void ReturnFactorCountDictionary(Dictionary<ulong, int> dictionary)
|
||||||
|
{
|
||||||
|
dictionary.Clear();
|
||||||
|
_factorCountDictionary = dictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static Dictionary<ulong, int>? _factorScratchDictionary;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Dictionary<ulong, int> RentFactorScratchDictionary()
|
||||||
|
{
|
||||||
|
Dictionary<ulong, int>? dictionary = _factorScratchDictionary;
|
||||||
|
if (dictionary is null)
|
||||||
|
{
|
||||||
|
return new Dictionary<ulong, int>(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
_factorScratchDictionary = null;
|
||||||
|
dictionary.Clear();
|
||||||
|
return dictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void ReturnFactorScratchDictionary(Dictionary<ulong, int> dictionary)
|
||||||
|
{
|
||||||
|
dictionary.Clear();
|
||||||
|
_factorScratchDictionary = dictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
@@ -218,7 +217,7 @@ public static class UInt128Extensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static bool IsBitSet(in GpuUInt128 value, int bitIndex)
|
private static bool IsBitSet(GpuUInt128 value, int bitIndex)
|
||||||
{
|
{
|
||||||
if (bitIndex >= 64)
|
if (bitIndex >= 64)
|
||||||
{
|
{
|
||||||
@@ -229,18 +228,16 @@ public static class UInt128Extensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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)
|
if (windowStart != 0)
|
||||||
{
|
{
|
||||||
GpuUInt128 shifted = exponent;
|
exponent.ShiftRight(windowStart);
|
||||||
shifted.ShiftRight(windowStart);
|
return exponent.Low & mask;
|
||||||
ulong mask = (1UL << windowBitCount) - 1UL;
|
|
||||||
return shifted.Low & mask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong directMask = (1UL << windowBitCount) - 1UL;
|
return exponent.Low & mask;
|
||||||
return exponent.Low & directMask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System.Collections.Concurrent;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using ILGPU;
|
||||||
using ILGPU.Algorithms;
|
using ILGPU.Algorithms;
|
||||||
|
using ILGPU.Runtime;
|
||||||
using PerfectNumbers.Core.Gpu;
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
namespace PerfectNumbers.Core;
|
||||||
@@ -239,20 +241,20 @@ public static class ULongExtensions
|
|||||||
// faster, while GPU code can keep using GpuUInt128.MulHigh.
|
// faster, while GPU code can keep using GpuUInt128.MulHigh.
|
||||||
ulong xLow = (uint)x;
|
ulong xLow = (uint)x;
|
||||||
ulong xHigh = x >> 32;
|
ulong xHigh = x >> 32;
|
||||||
ulong yLow = (uint)y;
|
ulong result = (uint)y;
|
||||||
ulong yHigh = y >> 32;
|
ulong yHigh = y >> 32;
|
||||||
|
|
||||||
ulong w1 = xLow * yHigh;
|
ulong w1 = xLow * yHigh;
|
||||||
ulong w2 = xHigh * yLow;
|
ulong w2 = xHigh * result;
|
||||||
ulong w3 = xLow * yLow;
|
xLow *= result;
|
||||||
|
|
||||||
// Matching the layout used in GpuUInt128.MulHigh: introducing the
|
// Matching the layout used in GpuUInt128.MulHigh: introducing the
|
||||||
// intermediate result looks like one extra store, but it lets RyuJIT keep
|
// intermediate result looks like one extra store, but it lets RyuJIT keep
|
||||||
// the accumulated high word entirely in registers. Without this explicit
|
// the accumulated high word entirely in registers. Without this explicit
|
||||||
// local the JIT spills the partial sum, which is where the performance
|
// local the JIT spills the partial sum, which is where the performance
|
||||||
// regression in the benchmarks came from.
|
// regression in the benchmarks came from.
|
||||||
ulong result = (xHigh * yHigh) + (w1 >> 32) + (w2 >> 32);
|
result = (xHigh * yHigh) + (w1 >> 32) + (w2 >> 32);
|
||||||
result += ((w3 >> 32) + (uint)w1 + (uint)w2) >> 32;
|
result += ((xLow >> 32) + (uint)w1 + (uint)w2) >> 32;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,9 +316,9 @@ public static class ULongExtensions
|
|||||||
{
|
{
|
||||||
ulong tLow = unchecked(a * b);
|
ulong tLow = unchecked(a * b);
|
||||||
ulong m = unchecked(tLow * nPrime);
|
ulong m = unchecked(tLow * nPrime);
|
||||||
ulong mTimesModulusLow = unchecked(m * modulus);
|
ulong result = unchecked(m * modulus);
|
||||||
|
|
||||||
ulong result = unchecked(a.MulHigh(b) + m.MulHigh(modulus) + (unchecked(tLow + mTimesModulusLow) < tLow ? 1UL : 0UL));
|
result = unchecked(a.MulHigh(b) + m.MulHigh(modulus) + (unchecked(tLow + result) < tLow ? 1UL : 0UL));
|
||||||
if (result >= modulus)
|
if (result >= modulus)
|
||||||
{
|
{
|
||||||
result -= modulus;
|
result -= modulus;
|
||||||
@@ -326,7 +328,7 @@ public static class ULongExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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;
|
ulong modulus = divisor.Modulus;
|
||||||
if (exponent == 0UL)
|
if (exponent == 0UL)
|
||||||
@@ -345,7 +347,10 @@ public static class ULongExtensions
|
|||||||
|
|
||||||
ulong result = divisor.MontgomeryOne;
|
ulong result = divisor.MontgomeryOne;
|
||||||
ulong nPrime = divisor.NPrime;
|
ulong nPrime = divisor.NPrime;
|
||||||
ulong[] oddPowers = InitializeMontgomeryOddPowers(divisor, modulus, nPrime, oddPowerCount);
|
|
||||||
|
ulong[] oddPowersArray = ThreadStaticPools.UlongPool.Rent(oddPowerCount);
|
||||||
|
Span<ulong> oddPowers = oddPowersArray.AsSpan(0, oddPowerCount);
|
||||||
|
InitializeMontgomeryOddPowersCpu(divisor, modulus, nPrime, oddPowers);
|
||||||
|
|
||||||
int index = bitLength - 1;
|
int index = bitLength - 1;
|
||||||
while (index >= 0)
|
while (index >= 0)
|
||||||
@@ -383,12 +388,185 @@ public static class ULongExtensions
|
|||||||
index = windowStart - 1;
|
index = windowStart - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keepMontgomery)
|
ThreadStaticPools.UlongPool.Return(oddPowersArray);
|
||||||
|
|
||||||
|
if (!keepMontgomery)
|
||||||
{
|
{
|
||||||
|
result = result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Pow2MontgomeryGpuExecutor.TryExecute(exponent, divisor, keepMontgomery, out ulong gpuResult))
|
||||||
|
{
|
||||||
|
return gpuResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pow2MontgomeryModWindowedCpu(exponent, divisor, keepMontgomery);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal static ulong Pow2MontgomeryModWindowedKernel(ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery)
|
||||||
|
{
|
||||||
|
ulong modulus = divisor.Modulus;
|
||||||
|
// This will never happen in production code
|
||||||
|
// 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);
|
||||||
|
ulong result = divisor.MontgomeryOne;
|
||||||
|
ulong nPrime = divisor.NPrime;
|
||||||
|
|
||||||
|
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;
|
||||||
|
// This can never happen in production code
|
||||||
|
// if (windowStart < 0)
|
||||||
|
// {
|
||||||
|
// windowStart = 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
while (((exponent >> windowStart) & 1UL) == 0UL)
|
||||||
|
{
|
||||||
|
windowStart++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitLength = index - windowStart + 1;
|
||||||
|
for (int square = 0; square < bitLength; square++)
|
||||||
|
{
|
||||||
|
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong windowValue = (1UL << bitLength) - 1UL;
|
||||||
|
windowValue = (exponent >> windowStart) & windowValue;
|
||||||
|
windowValue = ComputeMontgomeryOddPower(windowValue, divisor, modulus, nPrime);
|
||||||
|
result = result.MontgomeryMultiply(windowValue, modulus, nPrime);
|
||||||
|
|
||||||
|
index = windowStart - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keepMontgomery)
|
||||||
|
{
|
||||||
|
result = result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static ulong ComputeMontgomeryOddPower(ulong exponent, in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime)
|
||||||
|
{
|
||||||
|
ulong baseValue = divisor.MontgomeryTwo;
|
||||||
|
ulong power = divisor.MontgomeryOne;
|
||||||
|
ulong remaining = exponent;
|
||||||
|
|
||||||
|
while (remaining != 0UL)
|
||||||
|
{
|
||||||
|
if ((remaining & 1UL) != 0UL)
|
||||||
|
{
|
||||||
|
power = power.MontgomeryMultiply(baseValue, modulus, nPrime);
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining >>= 1;
|
||||||
|
if (remaining == 0UL)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
baseValue = baseValue.MontgomeryMultiply(baseValue, modulus, nPrime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return power;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Pow2MontgomeryGpuExecutor
|
||||||
|
{
|
||||||
|
private static readonly ConcurrentDictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, byte, ArrayView1D<ulong, Stride1D.Dense>>> KernelCache = new();
|
||||||
|
|
||||||
|
public static bool TryExecute(ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery, out ulong result)
|
||||||
|
{
|
||||||
|
result = 0UL;
|
||||||
|
|
||||||
|
GpuKernelLease lease;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var execution = lease.EnterExecutionScope();
|
||||||
|
Accelerator accelerator = lease.Accelerator;
|
||||||
|
if (accelerator.AcceleratorType == AcceleratorType.CPU)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var kernel = KernelCache.GetOrAdd(accelerator, static accel =>
|
||||||
|
{
|
||||||
|
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, byte, ArrayView1D<ulong, Stride1D.Dense>>(Pow2MontgomeryKernel);
|
||||||
|
var launcher = KernelUtil.GetKernel(loaded);
|
||||||
|
return launcher.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, byte, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||||
|
});
|
||||||
|
|
||||||
|
var exponentBuffer = accelerator.Allocate1D<ulong>(1);
|
||||||
|
var resultBuffer = accelerator.Allocate1D<ulong>(1);
|
||||||
|
|
||||||
|
exponentBuffer.View.CopyFromCPU(ref exponent, 1);
|
||||||
|
// We could clear the buffer, but it'll be overwritten anyway
|
||||||
|
// resultBuffer.MemSetToZero();
|
||||||
|
|
||||||
|
byte keepFlag = keepMontgomery ? (byte)1 : (byte)0;
|
||||||
|
AcceleratorStream stream = lease.Stream;
|
||||||
|
kernel(stream, 1, exponentBuffer.View, divisor, keepFlag, resultBuffer.View);
|
||||||
|
stream.Synchronize();
|
||||||
|
|
||||||
|
resultBuffer.View.CopyToCPU(ref result, 1);
|
||||||
|
resultBuffer.Dispose();
|
||||||
|
exponentBuffer.Dispose();
|
||||||
|
execution.Dispose();
|
||||||
|
lease.Dispose();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Pow2MontgomeryKernel(Index1D index, ArrayView1D<ulong, Stride1D.Dense> exponents, MontgomeryDivisorData divisor, byte keepMontgomery, ArrayView1D<ulong, Stride1D.Dense> results)
|
||||||
|
{
|
||||||
|
results[index] = Pow2MontgomeryModWindowedKernel(exponents[index], divisor, keepMontgomery != 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -422,76 +600,71 @@ public static class ULongExtensions
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static int GetPortableBitLength(ulong value)
|
private static int GetPortableBitLength(ulong value)
|
||||||
{
|
{
|
||||||
if (value == 0UL)
|
// This will never happen in production code
|
||||||
{
|
// if (value == 0UL)
|
||||||
return 0;
|
// {
|
||||||
}
|
// return 0;
|
||||||
|
// }
|
||||||
|
|
||||||
return 64 - XMath.LeadingZeroCount(value);
|
return 64 - XMath.LeadingZeroCount(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetWindowSize(int bitLength)
|
private static int GetWindowSize(int bitLength) => bitLength switch
|
||||||
{
|
{
|
||||||
if (bitLength <= 6)
|
<= 6 => bitLength,// bitLength is always >= 1 in production code
|
||||||
{
|
// return Math.Max(bitLength, 1);
|
||||||
return Math.Max(bitLength, 1);
|
<= 23 => 4,
|
||||||
}
|
<= 79 => 5,
|
||||||
|
<= 239 => 6,
|
||||||
if (bitLength <= 23)
|
<= 671 => 7,
|
||||||
{
|
_ => Pow2WindowSize,
|
||||||
return 4;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if (bitLength <= 79)
|
|
||||||
{
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bitLength <= 239)
|
|
||||||
{
|
|
||||||
return 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bitLength <= 671)
|
|
||||||
{
|
|
||||||
return 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Pow2WindowSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static ulong[] InitializeMontgomeryOddPowers(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, int oddPowerCount)
|
private static void InitializeMontgomeryOddPowersCpu(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, Span<ulong> oddPowers)
|
||||||
{
|
{
|
||||||
ulong[] oddPowers = new ulong[PerfectNumberConstants.MaxOddPowersCount];
|
|
||||||
oddPowers[0] = divisor.MontgomeryTwo;
|
oddPowers[0] = divisor.MontgomeryTwo;
|
||||||
if (oddPowerCount == 1)
|
if (oddPowers.Length == 1)
|
||||||
{
|
{
|
||||||
return oddPowers;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ulong previous;
|
||||||
ulong square = divisor.MontgomeryTwoSquared;
|
ulong square = divisor.MontgomeryTwoSquared;
|
||||||
for (int i = 1; i < oddPowerCount; i++)
|
|
||||||
|
for (int i = 1; i < oddPowers.Length; i++)
|
||||||
{
|
{
|
||||||
ulong previous = oddPowers[i - 1];
|
previous = oddPowers[i - 1];
|
||||||
oddPowers[i] = previous.MontgomeryMultiply(square, modulus, nPrime);
|
oddPowers[i] = previous.MontgomeryMultiply(square, modulus, nPrime);
|
||||||
}
|
}
|
||||||
|
|
||||||
return oddPowers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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;
|
ulong rotationCount = exponent % cycleLength;
|
||||||
return Pow2MontgomeryModWindowed(rotationCount, divisor, keepMontgomery: false);
|
return Pow2MontgomeryModWindowedCpu(rotationCount, divisor, keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static ulong Pow2MontgomeryModFromCycleRemainder(this ulong reducedExponent, in MontgomeryDivisorData divisor)
|
public static ulong Pow2MontgomeryModFromCycleRemainderCpu(this ulong reducedExponent, in MontgomeryDivisorData divisor)
|
||||||
{
|
{
|
||||||
return Pow2MontgomeryModWindowed(reducedExponent, divisor, keepMontgomery: false);
|
return Pow2MontgomeryModWindowedCpu(reducedExponent, divisor, keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ulong Pow2MontgomeryModWithCycleGpu(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor)
|
||||||
|
{
|
||||||
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static UInt128 PowMod(this ulong exponent, UInt128 modulus)
|
public static UInt128 PowMod(this ulong exponent, UInt128 modulus)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user