Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0466d98434 |
No files matched your search
Vendored
+1
-3
@@ -9,7 +9,5 @@
|
||||
"Mersenne",
|
||||
"primality",
|
||||
"suff"
|
||||
],
|
||||
"geminicodeassist.project": "evenperfectbitscanner",
|
||||
"todo-tree.tree.showCountsInTree": true
|
||||
]
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -32,7 +32,7 @@ public class GpuUInt128Montgomery64Benchmarks
|
||||
[Benchmark(Baseline = true)]
|
||||
public ulong ExtensionMontgomeryMultiply()
|
||||
{
|
||||
return Input.Left.MontgomeryMultiplyCpu(Input.Right, Input.Modulus, Input.NPrime);
|
||||
return Input.Left.MontgomeryMultiply(Input.Right, Input.Modulus, Input.NPrime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using PerfectNumbers.Core;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
public enum ModNumericType
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
@@ -100,7 +101,7 @@ public class Pow2ModBenchmarks
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertGpu(divisors[i]);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
@@ -639,7 +640,7 @@ public class Pow2ModBenchmarks
|
||||
ulong nPrime = ComputeMontgomeryNPrime(modulus);
|
||||
ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus);
|
||||
ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, modulus);
|
||||
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiplyCpu(montgomeryTwo, montgomeryTwo, modulus, nPrime);
|
||||
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiply(montgomeryTwo, montgomeryTwo, modulus, nPrime);
|
||||
|
||||
return new MontgomeryDivisorData(
|
||||
modulus,
|
||||
@@ -735,22 +736,22 @@ public class Pow2ModBenchmarks
|
||||
|
||||
if (exponent == 0UL)
|
||||
{
|
||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent);
|
||||
|
||||
for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||
|
||||
if (((exponent >> bitIndex) & 1UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(baseVal, modulus, nPrime);
|
||||
}
|
||||
}
|
||||
|
||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor)
|
||||
@@ -771,35 +772,35 @@ public class Pow2ModBenchmarks
|
||||
ulong nibble = remainingExponent & 0xFUL;
|
||||
|
||||
ulong base0 = baseVal;
|
||||
ulong base1 = base0.MontgomeryMultiplyCpu(base0, modulus, nPrime);
|
||||
ulong base2 = base1.MontgomeryMultiplyCpu(base1, modulus, nPrime);
|
||||
ulong base3 = base2.MontgomeryMultiplyCpu(base2, modulus, nPrime);
|
||||
ulong base1 = base0.MontgomeryMultiply(base0, modulus, nPrime);
|
||||
ulong base2 = base1.MontgomeryMultiply(base1, modulus, nPrime);
|
||||
ulong base3 = base2.MontgomeryMultiply(base2, modulus, nPrime);
|
||||
|
||||
if ((nibble & 1UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(base0, modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(base0, modulus, nPrime);
|
||||
}
|
||||
|
||||
if ((nibble & 2UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(base1, modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(base1, modulus, nPrime);
|
||||
}
|
||||
|
||||
if ((nibble & 4UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(base2, modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(base2, modulus, nPrime);
|
||||
}
|
||||
|
||||
if ((nibble & 8UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(base3, modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(base3, modulus, nPrime);
|
||||
}
|
||||
|
||||
baseVal = base3.MontgomeryMultiplyCpu(base3, modulus, nPrime);
|
||||
baseVal = base3.MontgomeryMultiply(base3, modulus, nPrime);
|
||||
remainingExponent >>= 4;
|
||||
}
|
||||
|
||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
private static ulong Pow2MontgomeryModSlidingWindow(ulong exponent, in MontgomeryDivisorData divisor)
|
||||
@@ -816,16 +817,16 @@ public class Pow2ModBenchmarks
|
||||
|
||||
if (exponent == 0UL)
|
||||
{
|
||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
const int WindowSize = 5;
|
||||
Span<ulong> oddPowers = stackalloc ulong[1 << (WindowSize - 1)];
|
||||
oddPowers[0] = baseVal;
|
||||
ulong baseSquared = baseVal.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
|
||||
ulong baseSquared = baseVal.MontgomeryMultiply(baseVal, modulus, nPrime);
|
||||
for (int i = 1; i < oddPowers.Length; i++)
|
||||
{
|
||||
oddPowers[i] = oddPowers[i - 1].MontgomeryMultiplyCpu(baseSquared, modulus, nPrime);
|
||||
oddPowers[i] = oddPowers[i - 1].MontgomeryMultiply(baseSquared, modulus, nPrime);
|
||||
}
|
||||
|
||||
int bitIndex = 63 - BitOperations.LeadingZeroCount(exponent);
|
||||
@@ -834,7 +835,7 @@ public class Pow2ModBenchmarks
|
||||
{
|
||||
if (((exponent >> bitIndex) & 1UL) == 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||
bitIndex--;
|
||||
continue;
|
||||
}
|
||||
@@ -851,14 +852,14 @@ public class Pow2ModBenchmarks
|
||||
int squares = bitIndex - windowStart + 1;
|
||||
for (int i = 0; i < squares; i++)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||
}
|
||||
|
||||
result = result.MontgomeryMultiplyCpu(oddPowers[(int)(windowValue >> 1)], modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(oddPowers[(int)(windowValue >> 1)], modulus, nPrime);
|
||||
bitIndex = windowStart - 1;
|
||||
}
|
||||
|
||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
private static ulong Pow2ModBinary(ulong exponent, ulong modulus)
|
||||
|
||||
@@ -77,7 +77,7 @@ public class Pow2MontgomeryModBenchmarks
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertGpu(divisors[i]);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
@@ -262,7 +262,7 @@ public class Pow2MontgomeryModBenchmarks
|
||||
ulong nPrime = ComputeMontgomeryNPrime(modulus);
|
||||
ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus);
|
||||
ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, modulus);
|
||||
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiplyCpu(montgomeryTwo, montgomeryTwo, modulus, nPrime);
|
||||
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiply(montgomeryTwo, montgomeryTwo, modulus, nPrime);
|
||||
|
||||
return new MontgomeryDivisorData(
|
||||
modulus,
|
||||
@@ -300,22 +300,22 @@ public class Pow2MontgomeryModBenchmarks
|
||||
|
||||
if (exponent == 0UL)
|
||||
{
|
||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent);
|
||||
|
||||
for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||
|
||||
if (((exponent >> bitIndex) & 1UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(baseVal, modulus, nPrime);
|
||||
}
|
||||
}
|
||||
|
||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor)
|
||||
@@ -336,35 +336,35 @@ public class Pow2MontgomeryModBenchmarks
|
||||
ulong nibble = remainingExponent & 0xFUL;
|
||||
|
||||
ulong base0 = baseVal;
|
||||
ulong base1 = base0.MontgomeryMultiplyCpu(base0, modulus, nPrime);
|
||||
ulong base2 = base1.MontgomeryMultiplyCpu(base1, modulus, nPrime);
|
||||
ulong base3 = base2.MontgomeryMultiplyCpu(base2, modulus, nPrime);
|
||||
ulong base1 = base0.MontgomeryMultiply(base0, modulus, nPrime);
|
||||
ulong base2 = base1.MontgomeryMultiply(base1, modulus, nPrime);
|
||||
ulong base3 = base2.MontgomeryMultiply(base2, modulus, nPrime);
|
||||
|
||||
if ((nibble & 1UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(base0, modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(base0, modulus, nPrime);
|
||||
}
|
||||
|
||||
if ((nibble & 2UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(base1, modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(base1, modulus, nPrime);
|
||||
}
|
||||
|
||||
if ((nibble & 4UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(base2, modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(base2, modulus, nPrime);
|
||||
}
|
||||
|
||||
if ((nibble & 8UL) != 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(base3, modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(base3, modulus, nPrime);
|
||||
}
|
||||
|
||||
baseVal = base3.MontgomeryMultiplyCpu(base3, modulus, nPrime);
|
||||
baseVal = base3.MontgomeryMultiply(base3, modulus, nPrime);
|
||||
remainingExponent >>= 4;
|
||||
}
|
||||
|
||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
// TODO: Update it with the latest implementations
|
||||
@@ -382,16 +382,16 @@ public class Pow2MontgomeryModBenchmarks
|
||||
|
||||
if (exponent == 0UL)
|
||||
{
|
||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
const int WindowSize = 5;
|
||||
Span<ulong> oddPowers = stackalloc ulong[1 << (WindowSize - 1)];
|
||||
oddPowers[0] = baseVal;
|
||||
ulong baseSquared = baseVal.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
|
||||
ulong baseSquared = baseVal.MontgomeryMultiply(baseVal, modulus, nPrime);
|
||||
for (int i = 1; i < oddPowers.Length; i++)
|
||||
{
|
||||
oddPowers[i] = oddPowers[i - 1].MontgomeryMultiplyCpu(baseSquared, modulus, nPrime);
|
||||
oddPowers[i] = oddPowers[i - 1].MontgomeryMultiply(baseSquared, modulus, nPrime);
|
||||
}
|
||||
|
||||
int bitIndex = 63 - BitOperations.LeadingZeroCount(exponent);
|
||||
@@ -400,7 +400,7 @@ public class Pow2MontgomeryModBenchmarks
|
||||
{
|
||||
if (((exponent >> bitIndex) & 1UL) == 0UL)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||
bitIndex--;
|
||||
continue;
|
||||
}
|
||||
@@ -417,14 +417,14 @@ public class Pow2MontgomeryModBenchmarks
|
||||
int squares = bitIndex - windowStart + 1;
|
||||
for (int i = 0; i < squares; i++)
|
||||
{
|
||||
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||
}
|
||||
|
||||
result = result.MontgomeryMultiplyCpu(oddPowers[(int)(windowValue >> 1)], modulus, nPrime);
|
||||
result = result.MontgomeryMultiply(oddPowers[(int)(windowValue >> 1)], modulus, nPrime);
|
||||
bitIndex = windowStart - 1;
|
||||
}
|
||||
|
||||
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
|
||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||
}
|
||||
|
||||
// TODO: Update it with the latest implementations, if it exists in production code. Otherwise just remove TODO
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
+3
-2
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using PerfectNumbers.Core;
|
||||
@@ -33,7 +34,7 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks
|
||||
for (int i = 0; i < cases.Length; i++)
|
||||
{
|
||||
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedConvertGpu(current.Divisor);
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedGpu(current.Divisor, keepMontgomery: false);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
@@ -51,7 +52,7 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks
|
||||
for (int i = 0; i < cases.Length; i++)
|
||||
{
|
||||
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedKeepGpu(current.Divisor);
|
||||
checksum ^= current.Exponent.Pow2MontgomeryModWindowedGpu(current.Divisor, keepMontgomery: true);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using Open.Numeric.Primes;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
@@ -10,7 +12,8 @@ namespace EvenPerfectBitScanner.Benchmarks;
|
||||
[MemoryDiagnoser]
|
||||
public class PrimeTesterBenchmarks
|
||||
{
|
||||
private static readonly HeuristicPrimeTester _heuristicTester = new();
|
||||
private HeuristicPrimeTester _cpuTester = null!;
|
||||
private HeuristicPrimeTester _gpuTester = null!;
|
||||
private ulong[] _candidates = [];
|
||||
|
||||
[ParamsSource(nameof(GetBenchmarkCases))]
|
||||
@@ -19,6 +22,8 @@ public class PrimeTesterBenchmarks
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
_cpuTester = new HeuristicPrimeTester();
|
||||
_gpuTester = new HeuristicPrimeTester();
|
||||
_candidates = BenchmarkCase.Candidates;
|
||||
|
||||
if (_candidates.Length == 0)
|
||||
@@ -26,23 +31,23 @@ public class PrimeTesterBenchmarks
|
||||
return;
|
||||
}
|
||||
|
||||
PrimeOrderCalculatorAccelerator.WarmUp();
|
||||
// HeuristicGroupABPrimeTesterAccelerator.WarmUp();
|
||||
_ = HeuristicPrimeTester.IsPrimeCpu(_candidates[0]);
|
||||
_ = HeuristicPrimeTester.IsPrimeGpu(_candidates[0]);
|
||||
CancellationToken cancellationToken = CancellationToken.None;
|
||||
_cpuTester.IsPrimeCpu(_candidates[0], cancellationToken);
|
||||
_gpuTester.IsPrimeGpu(_candidates[0]);
|
||||
_ = Prime.Numbers.IsPrime(_candidates[0]);
|
||||
}
|
||||
|
||||
// [Benchmark]
|
||||
[Benchmark(Baseline = true)]
|
||||
public int HeuristicCpu()
|
||||
{
|
||||
HeuristicPrimeTester tester = _heuristicTester;
|
||||
HeuristicPrimeTester tester = _cpuTester;
|
||||
ulong[] values = _candidates;
|
||||
CancellationToken cancellationToken = CancellationToken.None;
|
||||
int primeCount = 0;
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
if (HeuristicPrimeTester.IsPrimeCpu(values[i]))
|
||||
if (tester.IsPrimeCpu(values[i], cancellationToken))
|
||||
{
|
||||
primeCount++;
|
||||
}
|
||||
@@ -54,13 +59,14 @@ public class PrimeTesterBenchmarks
|
||||
[Benchmark]
|
||||
public int HeuristicGpu()
|
||||
{
|
||||
HeuristicPrimeTester tester = _heuristicTester;
|
||||
HeuristicPrimeTester tester = _gpuTester;
|
||||
ulong[] values = _candidates;
|
||||
CancellationToken cancellationToken = CancellationToken.None;
|
||||
int primeCount = 0;
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
if (HeuristicPrimeTester.IsPrimeGpu(values[i]))
|
||||
if (tester.IsPrimeGpu(values[i]))
|
||||
{
|
||||
primeCount++;
|
||||
}
|
||||
@@ -69,49 +75,18 @@ public class PrimeTesterBenchmarks
|
||||
return primeCount;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int HeuristicCombinedGpu()
|
||||
{
|
||||
ulong[] values = _candidates;
|
||||
int primeCount = 0;
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
if (HeuristicCombinedPrimeTester.IsPrimeGpu(values[i]))
|
||||
{
|
||||
primeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return primeCount;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int HeuristicCombinedCpu()
|
||||
{
|
||||
ulong[] values = _candidates;
|
||||
int primeCount = 0;
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
if (HeuristicCombinedPrimeTester.IsPrimeCpu(values[i]))
|
||||
{
|
||||
primeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return primeCount;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
// This benchmark is very slow. No point in comparing it.
|
||||
// [Benchmark]
|
||||
public int NonHeuristicGpu()
|
||||
{
|
||||
{
|
||||
// PrimeTester tester = _gpuTester;
|
||||
ulong[] values = _candidates;
|
||||
CancellationToken cancellationToken = CancellationToken.None;
|
||||
int primeCount = 0;
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
if (PrimeTester.IsPrimeGpu(values[i]))
|
||||
if (PrimeTester.Exclusive.IsPrimeGpu(values[i], cancellationToken))
|
||||
{
|
||||
primeCount++;
|
||||
}
|
||||
@@ -120,15 +95,17 @@ public class PrimeTesterBenchmarks
|
||||
return primeCount;
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
[Benchmark]
|
||||
public int NonHeuristicCpu()
|
||||
{
|
||||
// PrimeTester tester = _gpuTester;
|
||||
ulong[] values = _candidates;
|
||||
CancellationToken cancellationToken = CancellationToken.None;
|
||||
int primeCount = 0;
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
if (PrimeTester.IsPrime(values[i]))
|
||||
if (PrimeTester.IsPrimeCpu(values[i], cancellationToken))
|
||||
{
|
||||
primeCount++;
|
||||
}
|
||||
@@ -137,7 +114,7 @@ public class PrimeTesterBenchmarks
|
||||
return primeCount;
|
||||
}
|
||||
|
||||
// [Benchmark]
|
||||
[Benchmark]
|
||||
public int OpenNumericPrimes()
|
||||
{
|
||||
ulong[] values = _candidates;
|
||||
|
||||
@@ -19,7 +19,7 @@ internal static class Program
|
||||
"/help"
|
||||
};
|
||||
|
||||
private const string DefaultHeader = "p,divisor,searchedMersenne,detailedCheck,passedAllTests";
|
||||
private const string DefaultHeader = "p,searchedMersenne,detailedCheck,passedAllTests";
|
||||
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
|
||||
@@ -3,9 +3,13 @@ using Xunit;
|
||||
using System.Reflection;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
using EvenPerfectBitScanner;
|
||||
using EvenPerfectBitScanner.Candidates;
|
||||
using EvenPerfectBitScanner.Candidates.Transforms;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EvenPerfectBitScanner.Tests;
|
||||
|
||||
@@ -51,7 +55,7 @@ public class ProgramTests
|
||||
[InlineData(81UL, false)]
|
||||
public void IsPrime_identifies_primes_correctly(ulong n, bool expected)
|
||||
{
|
||||
PrimeTester.IsPrime(n).Should().Be(expected);
|
||||
new PrimeTester().IsPrime(n, CancellationToken.None).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -98,11 +102,13 @@ public class ProgramTests
|
||||
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||
|
||||
testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
|
||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||
candidatesField.SetValue(null, new (ulong, uint)[] { (7UL, 3U), (23UL, 11U) });
|
||||
forceCpuProp!.SetValue(null, true);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -116,6 +122,7 @@ public class ProgramTests
|
||||
candidatesField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -128,11 +135,13 @@ public class ProgramTests
|
||||
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||
|
||||
testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
|
||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||
candidatesField.SetValue(null, Array.Empty<(ulong, uint)>());
|
||||
forceCpuProp!.SetValue(null, true);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -146,6 +155,7 @@ public class ProgramTests
|
||||
candidatesField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -158,10 +168,12 @@ public class ProgramTests
|
||||
|
||||
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||
|
||||
testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
|
||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||
forceCpuProp!.SetValue(null, true);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -174,6 +186,7 @@ public class ProgramTests
|
||||
testerField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -186,10 +199,12 @@ public class ProgramTests
|
||||
|
||||
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||
|
||||
testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
|
||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -206,6 +221,7 @@ public class ProgramTests
|
||||
testerField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -217,10 +233,12 @@ public class ProgramTests
|
||||
|
||||
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||
|
||||
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(useIncremental: true, useResidue: true, maxK: 1_000UL), trackAllValues: true));
|
||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -237,6 +255,7 @@ public class ProgramTests
|
||||
mersenneField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -248,6 +267,7 @@ public class ProgramTests
|
||||
|
||||
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||
|
||||
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||
useIncremental: true,
|
||||
@@ -255,6 +275,7 @@ public class ProgramTests
|
||||
maxK: 1_024UL), trackAllValues: true));
|
||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -271,6 +292,7 @@ public class ProgramTests
|
||||
mersenneField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -283,6 +305,7 @@ public class ProgramTests
|
||||
|
||||
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||
|
||||
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||
useIncremental: true,
|
||||
@@ -290,6 +313,7 @@ public class ProgramTests
|
||||
maxK: 5_000_000UL), trackAllValues: true));
|
||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -306,6 +330,7 @@ public class ProgramTests
|
||||
mersenneField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -317,6 +342,7 @@ public class ProgramTests
|
||||
|
||||
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||
|
||||
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||
useIncremental: true,
|
||||
@@ -324,6 +350,7 @@ public class ProgramTests
|
||||
maxK: 5_000_000UL), trackAllValues: true));
|
||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -340,6 +367,7 @@ public class ProgramTests
|
||||
mersenneField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -352,6 +380,7 @@ public class ProgramTests
|
||||
|
||||
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||
|
||||
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||
useIncremental: true,
|
||||
@@ -361,6 +390,7 @@ public class ProgramTests
|
||||
maxK: 1_024UL), trackAllValues: true));
|
||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
|
||||
forceCpuProp!.SetValue(null, true);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -372,6 +402,7 @@ public class ProgramTests
|
||||
mersenneField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
@@ -386,6 +417,7 @@ public class ProgramTests
|
||||
|
||||
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||
|
||||
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
|
||||
useIncremental: false,
|
||||
@@ -396,6 +428,7 @@ public class ProgramTests
|
||||
maxK: configuration.ResidueMaxK), trackAllValues: true));
|
||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -407,7 +440,9 @@ public class ProgramTests
|
||||
mersenneField.SetValue(null, null);
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,10 +506,12 @@ public class ProgramTests
|
||||
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var compositeField = typeof(Program).GetField("_lastCompositeP", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
|
||||
|
||||
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(useIncremental: true, useResidue: false, maxK: 1_000UL), trackAllValues: true));
|
||||
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
|
||||
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
|
||||
forceCpuProp!.SetValue(null, true);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -495,6 +532,7 @@ public class ProgramTests
|
||||
primeField.SetValue(null, null);
|
||||
CandidatesCalculator.ResetResidueTrackers();
|
||||
compositeField.SetValue(null, false);
|
||||
forceCpuProp!.SetValue(null, false);
|
||||
ResetCliArguments();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
using System.Buffers.Text;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace EvenPerfectBitScanner;
|
||||
|
||||
internal static class CalculationResultHandler
|
||||
{
|
||||
private const string ResultsHeader = "p,divisor,searchedMersenne,detailedCheck,passedAllTests";
|
||||
private const string ResultsHeader = "p,searchedMersenne,detailedCheck,passedAllTests";
|
||||
private const string PrimeFoundSuffix = " (FOUND VALID CANDIDATES)";
|
||||
internal const int DefaultWriteBatchSize = 100;
|
||||
|
||||
@@ -93,19 +95,19 @@ internal static class CalculationResultHandler
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void HandleResult(ulong currentP, ulong divisor, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite)
|
||||
internal static void HandleResult(ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite)
|
||||
{
|
||||
// Program flow ensures Initialize(...) and InitializeOutputBuffer() ran before this call. Do not add initialization guards here.
|
||||
|
||||
Span<byte> stackBuffer = stackalloc byte[128];
|
||||
byte[]? rentedBuffer = null;
|
||||
Span<byte> utf8Span = stackBuffer;
|
||||
int byteCount = TryFormatResultLine(utf8Span, currentP, divisor, searchedMersenne, detailedCheck, passedAllTests);
|
||||
int byteCount = TryFormatResultLine(utf8Span, currentP, searchedMersenne, detailedCheck, passedAllTests);
|
||||
if (byteCount < 0)
|
||||
{
|
||||
rentedBuffer = ThreadStaticPools.BytePool.Rent(256);
|
||||
utf8Span = rentedBuffer;
|
||||
byteCount = TryFormatResultLine(utf8Span, currentP, divisor, searchedMersenne, detailedCheck, passedAllTests);
|
||||
byteCount = TryFormatResultLine(utf8Span, currentP, searchedMersenne, detailedCheck, passedAllTests);
|
||||
if (byteCount < 0)
|
||||
{
|
||||
ThreadStaticPools.BytePool.Return(rentedBuffer);
|
||||
@@ -229,7 +231,7 @@ internal static class CalculationResultHandler
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int TryFormatResultLine(Span<byte> destination, ulong currentP, ulong divisor, bool searchedMersenne, bool detailedCheck, bool passedAllTests)
|
||||
private static int TryFormatResultLine(Span<byte> destination, ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests)
|
||||
{
|
||||
int offset = 0;
|
||||
|
||||
@@ -246,19 +248,6 @@ internal static class CalculationResultHandler
|
||||
|
||||
destination[offset++] = (byte)',';
|
||||
|
||||
if (!Utf8Formatter.TryFormat(divisor, destination[offset..], out written))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
offset += written;
|
||||
if (offset >= destination.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
destination[offset++] = (byte)',';
|
||||
|
||||
if (!Utf8Formatter.TryFormat(searchedMersenne, destination[offset..], out written))
|
||||
{
|
||||
return -1;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
namespace EvenPerfectBitScanner.Candidates;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using Open.Numeric.Primes;
|
||||
using PerfectNumbers.Core;
|
||||
using EvenPerfectBitScanner.Candidates.Transforms;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using EvenPerfectBitScanner.Candidates;
|
||||
using Open.Numeric.Primes;
|
||||
|
||||
namespace EvenPerfectBitScanner.Candidates.Transforms;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using EvenPerfectBitScanner.Candidates;
|
||||
|
||||
namespace EvenPerfectBitScanner.Candidates.Transforms;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace EvenPerfectBitScanner.Candidates.Transforms;
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
@@ -200,15 +202,14 @@ internal readonly struct CliArguments
|
||||
|
||||
if (argument.StartsWith("--prime=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> primeValue = argument.AsSpan("--prime=".Length);
|
||||
startPrime = Utf8CliParser.ParseUInt64(primeValue);
|
||||
startPrime = Utf8CliParser.ParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1));
|
||||
startPrimeProvided = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--max-prime=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = argument.AsSpan("--max-prime=".Length);
|
||||
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||
if (UInt128.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out UInt128 parsedMaxPrime))
|
||||
{
|
||||
maxPrimeLimit = parsedMaxPrime;
|
||||
@@ -237,13 +238,13 @@ internal readonly struct CliArguments
|
||||
|
||||
if (argument.StartsWith("--threads=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
threadCount = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--threads=".Length)));
|
||||
threadCount = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--mersenne=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = argument.AsSpan("--mersenne=".Length);
|
||||
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||
if (value.Equals("pow2mod", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
kernelType = GpuKernelType.Pow2Mod;
|
||||
@@ -280,7 +281,7 @@ internal readonly struct CliArguments
|
||||
|
||||
if (argument.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Utf8CliParser.TryParseUInt64(argument.AsSpan("--divisor-cycles-limit=".Length), out ulong parsedLimit))
|
||||
if (Utf8CliParser.TryParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1), out ulong parsedLimit))
|
||||
{
|
||||
divisorCyclesSearchLimit = parsedLimit;
|
||||
}
|
||||
@@ -290,7 +291,7 @@ internal readonly struct CliArguments
|
||||
|
||||
if (argument.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Utf8CliParser.TryParseUInt64(argument.AsSpan("--residue-max-k=".Length), out ulong parsedResidueMax))
|
||||
if (Utf8CliParser.TryParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1), out ulong parsedResidueMax))
|
||||
{
|
||||
residueKMax = parsedResidueMax;
|
||||
}
|
||||
@@ -300,7 +301,7 @@ internal readonly struct CliArguments
|
||||
|
||||
if (argument.StartsWith("--mersenne-device=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useMersenneOnGpu = !argument.AsSpan("--mersenne-device=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
useMersenneOnGpu = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -318,7 +319,7 @@ internal readonly struct CliArguments
|
||||
|
||||
if (argument.StartsWith("--order-warmup-limit=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Utf8CliParser.TryParseUInt64(argument.AsSpan("--order-warmup-limit=".Length), out ulong parsedLimit))
|
||||
if (Utf8CliParser.TryParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1), out ulong parsedLimit))
|
||||
{
|
||||
orderWarmupLimitOverride = parsedLimit;
|
||||
}
|
||||
@@ -334,14 +335,14 @@ internal readonly struct CliArguments
|
||||
|
||||
if (argument.StartsWith("--ntt=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = argument.AsSpan("--ntt=".Length);
|
||||
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||
nttBackend = value.Equals("staged", StringComparison.OrdinalIgnoreCase) ? NttBackend.Staged : NttBackend.Reference;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--mod-reduction=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = argument.AsSpan("--mod-reduction=".Length);
|
||||
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||
if (value.Equals("mont64", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
modReductionMode = ModReductionMode.Mont64;
|
||||
@@ -364,25 +365,25 @@ internal readonly struct CliArguments
|
||||
|
||||
if (argument.StartsWith("--primes-device=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
forcePrimeKernelsOnCpu = argument.AsSpan("--primes-device=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
forcePrimeKernelsOnCpu = argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--order-device=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useOrderOnGpu = !argument.AsSpan("--order-device=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
useOrderOnGpu = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--rle-blacklist=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
rleBlacklistPath = argument["--rle-blacklist=".Length..];
|
||||
rleBlacklistPath = argument[(argument.IndexOf('=') + 1)..];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--rle-hard-max=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Utf8CliParser.TryParseUInt64(argument.AsSpan("--rle-hard-max=".Length), out ulong parsedMax))
|
||||
if (Utf8CliParser.TryParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1), out ulong parsedMax))
|
||||
{
|
||||
rleHardMaxP = parsedMax;
|
||||
}
|
||||
@@ -392,14 +393,14 @@ internal readonly struct CliArguments
|
||||
|
||||
if (argument.StartsWith("--rle-only-last7=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = argument.AsSpan("--rle-only-last7=".Length);
|
||||
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||
rleOnlyLast7 = !value.Equals("false", StringComparison.OrdinalIgnoreCase) && !value.Equals("0", StringComparison.OrdinalIgnoreCase);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--zero-hard=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Utf8CliParser.TryParseDouble(argument.AsSpan("--zero-hard=".Length), out double zeroFraction))
|
||||
if (Utf8CliParser.TryParseDouble(argument.AsSpan(argument.IndexOf('=') + 1), out double zeroFraction))
|
||||
{
|
||||
zeroFractionHard = zeroFraction;
|
||||
}
|
||||
@@ -409,7 +410,7 @@ internal readonly struct CliArguments
|
||||
|
||||
if (argument.StartsWith("--zero-conj=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ReadOnlySpan<char> value = argument.AsSpan("--zero-conj=".Length);
|
||||
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1);
|
||||
int colon = value.IndexOf(':');
|
||||
if (colon > 0)
|
||||
{
|
||||
@@ -426,97 +427,87 @@ internal readonly struct CliArguments
|
||||
|
||||
if (argument.StartsWith("--lucas=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useMersenneOnGpu = !argument.AsSpan("--lucas=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
useMersenneOnGpu = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--primes=", StringComparison.OrdinalIgnoreCase))
|
||||
if (argument.StartsWith("--primes=", StringComparison.OrdinalIgnoreCase) ||
|
||||
argument.StartsWith("--gpu-kernels=", StringComparison.OrdinalIgnoreCase) ||
|
||||
argument.StartsWith("--accelerator=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
forcePrimeKernelsOnCpu = argument.AsSpan("--primes=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--gpu-kernels=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
forcePrimeKernelsOnCpu = argument.AsSpan("--gpu-kernels=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--accelerator=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
forcePrimeKernelsOnCpu = argument.AsSpan("--accelerator=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
forcePrimeKernelsOnCpu = argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--results-dir=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
resultsDirectory = argument["--results-dir=".Length..];
|
||||
resultsDirectory = argument[(argument.IndexOf('=') + 1)..];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--results-prefix=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
resultsPrefix = argument["--results-prefix=".Length..];
|
||||
resultsPrefix = argument[(argument.IndexOf('=') + 1)..];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--gpu-prime-threads=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
gpuPrimeThreads = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--gpu-prime-threads=".Length)));
|
||||
gpuPrimeThreads = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--gpu-prime-batch=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
gpuPrimeBatch = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--gpu-prime-batch=".Length)));
|
||||
gpuPrimeBatch = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--ll-slice=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
sliceSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--ll-slice=".Length)));
|
||||
sliceSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--gpu-scan-batch=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
scanBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--gpu-scan-batch=".Length)));
|
||||
scanBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--block-size=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
blockSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--block-size=".Length)));
|
||||
blockSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--filter-p=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
filterFile = argument["--filter-p=".Length..];
|
||||
filterFile = argument[(argument.IndexOf('=') + 1)..];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--write-batch-size=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
writeBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--write-batch-size=".Length)));
|
||||
writeBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--divisor-cycles=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
cyclesPath = argument["--divisor-cycles=".Length..];
|
||||
cyclesPath = argument[(argument.IndexOf('=') + 1)..];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--divisor-cycles-device=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
useGpuCycles = !argument.AsSpan("--divisor-cycles-device=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
useGpuCycles = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.StartsWith("--divisor-cycles-batch=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
cyclesBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--divisor-cycles-batch=".Length)));
|
||||
cyclesBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1)));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Open.Collections;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using EvenPerfectBitScanner;
|
||||
|
||||
namespace EvenPerfectBitScanner.IO;
|
||||
|
||||
@@ -13,7 +14,7 @@ internal static class CalculationResultsFile
|
||||
using FileStream readStream = new(candidateFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using StreamReader reader = new(readStream);
|
||||
string? line;
|
||||
ReadOnlySpan<char> trimmed;
|
||||
ReadOnlySpan<char> span;
|
||||
while ((line = reader.ReadLine()) is not null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
@@ -21,67 +22,36 @@ internal static class CalculationResultsFile
|
||||
continue;
|
||||
}
|
||||
|
||||
trimmed = line.AsSpan().Trim();
|
||||
if (trimmed.IsEmpty || trimmed[0] == '#')
|
||||
span = line.AsSpan().Trim();
|
||||
if (span.IsEmpty || span[0] == '#')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int length = trimmed.Length;
|
||||
int index = 0;
|
||||
if (!maxPrimeConfigured)
|
||||
while (index < span.Length)
|
||||
{
|
||||
while (index < length)
|
||||
if (!char.IsDigit(span[index]))
|
||||
{
|
||||
char current = trimmed[index];
|
||||
uint digit = (uint)(current - '0');
|
||||
if (digit > 9U)
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int start = index++;
|
||||
while (index < length && (uint)(trimmed[index] - '0') <= 9U)
|
||||
{
|
||||
index++;
|
||||
}
|
||||
int start = index;
|
||||
index++;
|
||||
while (index < span.Length && char.IsDigit(span[index]))
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
if (Utf8CliParser.TryParseUInt64(trimmed[start..index], out ulong parsed))
|
||||
if (Utf8CliParser.TryParseUInt64(span[start..index], out ulong parsed))
|
||||
{
|
||||
if (!maxPrimeConfigured || (UInt128)parsed <= maxPrimeLimit)
|
||||
{
|
||||
candidates.Add(parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UInt128 limit = maxPrimeLimit;
|
||||
while (index < length)
|
||||
{
|
||||
char current = trimmed[index];
|
||||
uint digit = (uint)(current - '0');
|
||||
if (digit > 9U)
|
||||
else
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int start = index++;
|
||||
while (index < length && (uint)(trimmed[index] - '0') <= 9U)
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
if (Utf8CliParser.TryParseUInt64(trimmed[start..index], out ulong parsed))
|
||||
{
|
||||
if ((UInt128)parsed <= limit)
|
||||
{
|
||||
candidates.Add(parsed);
|
||||
}
|
||||
else
|
||||
{
|
||||
skippedByLimit++;
|
||||
}
|
||||
skippedByLimit++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,7 +70,6 @@ internal static class CalculationResultsFile
|
||||
int first = 0;
|
||||
int second = 0;
|
||||
int third = 0;
|
||||
int fourth = 0;
|
||||
ulong parsedP = 0UL;
|
||||
ReadOnlySpan<char> detailedSpan = default;
|
||||
ReadOnlySpan<char> passedAllTestsSpan = default;
|
||||
@@ -140,15 +109,8 @@ internal static class CalculationResultsFile
|
||||
continue;
|
||||
}
|
||||
|
||||
span = span[(third + 1)..];
|
||||
fourth = span.IndexOf(',');
|
||||
if (fourth < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
detailedSpan = span[..fourth];
|
||||
passedAllTestsSpan = span[(fourth + 1)..];
|
||||
detailedSpan = span[..third];
|
||||
passedAllTestsSpan = span[(third + 1)..];
|
||||
|
||||
if (Utf8CliParser.TryParseBoolean(detailedSpan, out detailed) && Utf8CliParser.TryParseBoolean(passedAllTestsSpan, out passedAllTests))
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace EvenPerfectBitScanner;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
// private static ThreadLocal<PrimeTester> PrimeTesters = null!;
|
||||
private static ThreadLocal<PrimeTester> PrimeTesters = null!;
|
||||
private static ThreadLocal<MersenneNumberTester> MersenneTesters = null!;
|
||||
private const ulong InitialP = PerfectNumberConstants.BiggestKnownEvenPerfectP;
|
||||
private static MersenneNumberDivisorGpuTester? _divisorTester;
|
||||
@@ -28,8 +28,6 @@ internal static class Program
|
||||
[ThreadStatic]
|
||||
private static bool _lastCompositeP;
|
||||
|
||||
private static bool _runPrimesOnCpu;
|
||||
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
try
|
||||
@@ -51,24 +49,18 @@ internal static class Program
|
||||
|
||||
NttGpuMath.GpuTransformBackend = _cliArguments.NttBackend;
|
||||
NttGpuMath.ReductionMode = _cliArguments.ModReductionMode;
|
||||
_runPrimesOnCpu = _cliArguments.ForcePrimeKernelsOnCpu;
|
||||
GpuContextPool.ForceCpu = _cliArguments.ForcePrimeKernelsOnCpu;
|
||||
|
||||
ulong currentP = _cliArguments.StartPrime;
|
||||
ulong remainder = currentP % 6UL;
|
||||
bool startPrimeProvided = _cliArguments.StartPrimeProvided;
|
||||
int threadCount = Math.Max(1, _cliArguments.ThreadCount);
|
||||
int gpuPrimeBatch = Math.Max(1, _cliArguments.GpuPrimeBatch);
|
||||
// int gpuPrimeThreads = SharedGpuContext.Device.MaxNumThreads;
|
||||
int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads);
|
||||
UnboundedTaskScheduler.ConfigureThreadCount(threadCount);
|
||||
PrimeTester.GpuBatchSize = gpuPrimeBatch;
|
||||
GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads);
|
||||
Console.WriteLine("Warming up GPU kernels");
|
||||
PrimeTester.WarmUpGpuKernels(1024);
|
||||
// PrimeTester.WarmUpGpuKernels(gpuPrimeThreads >> 4);
|
||||
Console.WriteLine("Starting up threads...");
|
||||
_ = UnboundedTaskScheduler.Instance;
|
||||
int blockSize = Math.Max(1, _cliArguments.BlockSize);
|
||||
int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads);
|
||||
int gpuPrimeBatch = Math.Max(1, _cliArguments.GpuPrimeBatch);
|
||||
GpuKernelType kernelType = _cliArguments.KernelType;
|
||||
bool useBitTransform = _cliArguments.UseBitTransform;
|
||||
bool useOrder = _cliArguments.UseOrder;
|
||||
@@ -112,7 +104,14 @@ internal static class Program
|
||||
testPrimeCandidateLimit = Math.Max(1, threadCount) * 3;
|
||||
}
|
||||
|
||||
_byDivisorStartPrime = useByDivisor && startPrimeProvided ? currentP : 0UL;
|
||||
if (useByDivisor)
|
||||
{
|
||||
_byDivisorStartPrime = startPrimeProvided ? currentP : 0UL;
|
||||
}
|
||||
else
|
||||
{
|
||||
_byDivisorStartPrime = 0UL;
|
||||
}
|
||||
|
||||
if (useByDivisor && !testMode && string.IsNullOrEmpty(filterFile))
|
||||
{
|
||||
@@ -120,6 +119,9 @@ internal static class Program
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply GPU prime sieve runtime configuration
|
||||
GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads);
|
||||
PrimeTester.GpuBatchSize = Math.Max(1, gpuPrimeBatch);
|
||||
|
||||
MersenneDivisorCycles mersenneDivisorCycles = new();
|
||||
if (!File.Exists(cyclesPath) || continueCyclesGeneration)
|
||||
@@ -175,10 +177,13 @@ internal static class Program
|
||||
|
||||
Console.WriteLine("Divisor cycles are ready");
|
||||
|
||||
blockSize = useByDivisor ? 1 : blockSize;
|
||||
if (useByDivisor)
|
||||
{
|
||||
blockSize = 1;
|
||||
}
|
||||
|
||||
CandidatesCalculator.Initialize(currentP);
|
||||
// PrimeTesters = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true);
|
||||
PrimeTesters = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true);
|
||||
// Note: --primes-device controls default device for library kernels; p primality remains CPU here.
|
||||
// Initialize per-thread p residue tracker (Identity model) at currentP
|
||||
if (!useDivisor && !_cliArguments.UseByDivisor)
|
||||
@@ -261,7 +266,7 @@ internal static class Program
|
||||
orderWarmupLimitOverride ?? 5_000_000UL,
|
||||
NttGpuMath.ReductionMode,
|
||||
mersenneOnGpu ? "gpu" : "cpu",
|
||||
_runPrimesOnCpu ? "cpu" : "gpu",
|
||||
GpuContextPool.ForceCpu ? "cpu" : "gpu",
|
||||
orderOnGpu ? "gpu" : "cpu");
|
||||
|
||||
if (!string.IsNullOrEmpty(_cliArguments.ResultsPrefix))
|
||||
@@ -422,7 +427,7 @@ internal static class Program
|
||||
_byDivisorStartPrime,
|
||||
static () => _lastCompositeP = true,
|
||||
static () => _lastCompositeP = false,
|
||||
static (candidate, searchedMersenne, detailedCheck, passedAllTests, divisor) => CalculationResultHandler.HandleResult(candidate, divisor, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP),
|
||||
static (candidate, searchedMersenne, detailedCheck, passedAllTests) => CalculationResultHandler.HandleResult(candidate, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP),
|
||||
threadCount,
|
||||
byDivisorPrimeRange);
|
||||
|
||||
@@ -483,7 +488,7 @@ internal static class Program
|
||||
{
|
||||
p = buffer[j];
|
||||
passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck);
|
||||
CalculationResultHandler.HandleResult(p, 0UL, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP);
|
||||
CalculationResultHandler.HandleResult(p, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP);
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -506,7 +511,7 @@ internal static class Program
|
||||
}
|
||||
|
||||
passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck);
|
||||
CalculationResultHandler.HandleResult(p, 0UL, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP);
|
||||
CalculationResultHandler.HandleResult(p, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP);
|
||||
|
||||
if (p == maxP)
|
||||
{
|
||||
@@ -626,9 +631,9 @@ internal static class Program
|
||||
// If primes-device=gpu, route p primality through GPU-assisted sieve with deterministic MR validation.
|
||||
if (!_cliArguments.UseByDivisor)
|
||||
{
|
||||
if (!(_runPrimesOnCpu
|
||||
? PrimeTester.IsPrime(p)
|
||||
: HeuristicCombinedPrimeTester.IsPrimeGpu(p)))
|
||||
if (!(GpuContextPool.ForceCpu
|
||||
? PrimeTesters.Value!.IsPrime(p, CancellationToken.None)
|
||||
: PrimeTesters.Value!.IsPrimeGpu(p, CancellationToken.None)))
|
||||
{
|
||||
_lastCompositeP = true;
|
||||
return false;
|
||||
@@ -649,7 +654,7 @@ internal static class Program
|
||||
if (_cliArguments.UseByDivisor)
|
||||
{
|
||||
// Windowed pow2mod kernel handles by-divisor scans.
|
||||
return _byDivisorTester!.IsPrime(p, out detailedCheck, out _);
|
||||
return _byDivisorTester!.IsPrime(p, out detailedCheck);
|
||||
}
|
||||
|
||||
detailedCheck = MersenneTesters.Value!.IsMersennePrime(p);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Text;
|
||||
using System.Text;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
using FluentAssertions;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
@@ -5,88 +9,88 @@ namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
[Collection("GpuNtt")]
|
||||
public class GpuContextPoolTests
|
||||
{
|
||||
// [Fact]
|
||||
// [Trait("Category", "Fast")]
|
||||
// public void RentPreferred_cpu_returns_cpu_accelerator()
|
||||
// {
|
||||
// GpuContextPool.DisposeAll();
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void RentPreferred_cpu_returns_cpu_accelerator()
|
||||
{
|
||||
GpuContextPool.DisposeAll();
|
||||
|
||||
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
// try
|
||||
// {
|
||||
// lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// lease.Dispose();
|
||||
// }
|
||||
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
try
|
||||
{
|
||||
lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
|
||||
// GpuContextPool.DisposeAll();
|
||||
// }
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
|
||||
// [Fact]
|
||||
// [Trait("Category", "Fast")]
|
||||
// public void RentPreferred_reuses_cpu_context()
|
||||
// {
|
||||
// GpuContextPool.DisposeAll();
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void RentPreferred_reuses_cpu_context()
|
||||
{
|
||||
GpuContextPool.DisposeAll();
|
||||
|
||||
// Context firstContext;
|
||||
// Accelerator firstAccelerator;
|
||||
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
// try
|
||||
// {
|
||||
// firstContext = lease.Context;
|
||||
// firstAccelerator = lease.Accelerator;
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// lease.Dispose();
|
||||
// }
|
||||
Context firstContext;
|
||||
Accelerator firstAccelerator;
|
||||
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
try
|
||||
{
|
||||
firstContext = lease.Context;
|
||||
firstAccelerator = lease.Accelerator;
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
|
||||
// lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
// try
|
||||
// {
|
||||
// lease.Context.Should().BeSameAs(firstContext);
|
||||
// lease.Accelerator.Should().BeSameAs(firstAccelerator);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// lease.Dispose();
|
||||
// }
|
||||
lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
try
|
||||
{
|
||||
lease.Context.Should().BeSameAs(firstContext);
|
||||
lease.Accelerator.Should().BeSameAs(firstAccelerator);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
|
||||
// GpuContextPool.DisposeAll();
|
||||
// }
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
|
||||
// [Fact]
|
||||
// [Trait("Category", "Fast")]
|
||||
// public void DisposeAll_clears_pooled_contexts()
|
||||
// {
|
||||
// GpuContextPool.DisposeAll();
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void DisposeAll_clears_pooled_contexts()
|
||||
{
|
||||
GpuContextPool.DisposeAll();
|
||||
|
||||
// Context firstContext;
|
||||
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
// try
|
||||
// {
|
||||
// firstContext = lease.Context;
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// lease.Dispose();
|
||||
// }
|
||||
Context firstContext;
|
||||
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
try
|
||||
{
|
||||
firstContext = lease.Context;
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
|
||||
// GpuContextPool.DisposeAll();
|
||||
GpuContextPool.DisposeAll();
|
||||
|
||||
// lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
// try
|
||||
// {
|
||||
// lease.Context.Should().NotBeSameAs(firstContext);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// lease.Dispose();
|
||||
// }
|
||||
lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
try
|
||||
{
|
||||
lease.Context.Should().NotBeSameAs(firstContext);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
|
||||
// GpuContextPool.DisposeAll();
|
||||
// }
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Reflection;
|
||||
using FluentAssertions;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
@@ -114,10 +114,14 @@ public class GpuKernelCompilationTests
|
||||
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModKernel not found.");
|
||||
|
||||
private static readonly MethodInfo PrimeOrderGetPow2ModWideKernelMethod =
|
||||
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModWideKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModWideKernel not found.");
|
||||
|
||||
private static readonly Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> MersenneDivisorCyclesKernel =
|
||||
DivisorCycleKernels.GpuDivisorCycleKernel;
|
||||
|
||||
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>> PrimeTesterSmallPrimeKernel =
|
||||
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>> PrimeTesterSmallPrimeKernel =
|
||||
PrimeTesterKernels.SmallPrimeSieveKernel;
|
||||
|
||||
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<byte>> PrimeTesterSharesFactorKernel =
|
||||
@@ -163,23 +167,23 @@ public class GpuKernelCompilationTests
|
||||
return new object[] { name, loader };
|
||||
}
|
||||
|
||||
// [Theory]
|
||||
// [Trait("Category", "Fast")]
|
||||
// [MemberData(nameof(KernelLoaders))]
|
||||
// public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile)
|
||||
// {
|
||||
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
// try
|
||||
// {
|
||||
// var accelerator = lease.Accelerator;
|
||||
// Action action = () => compile(accelerator);
|
||||
// action.Should().NotThrow($"kernel {name} should compile");
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// lease.Dispose();
|
||||
// }
|
||||
// }
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[MemberData(nameof(KernelLoaders))]
|
||||
public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile)
|
||||
{
|
||||
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
try
|
||||
{
|
||||
var accelerator = lease.Accelerator;
|
||||
Action action = () => compile(accelerator);
|
||||
action.Should().NotThrow($"kernel {name} should compile");
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static void CompileDivisorCycleCacheKernel(Accelerator accelerator)
|
||||
{
|
||||
@@ -321,67 +325,56 @@ public class GpuKernelCompilationTests
|
||||
|
||||
private static void CompilePrimeOrderPow2ModWideKernel(Accelerator accelerator)
|
||||
{
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>>(PrimeOrderGpuHeuristics.Pow2ModKernelWide);
|
||||
_ = kernel;
|
||||
_ = PrimeOrderGetPow2ModWideKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileMersenneDivisorCyclesKernel(Accelerator accelerator)
|
||||
{
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel(MersenneDivisorCyclesKernel);
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(MersenneDivisorCyclesKernel);
|
||||
_ = kernel;
|
||||
}
|
||||
|
||||
private static void CompilePrimeTesterSmallPrimeKernel(Accelerator accelerator)
|
||||
{
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel(PrimeTesterSmallPrimeKernel);
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>>(PrimeTesterSmallPrimeKernel);
|
||||
_ = kernel;
|
||||
}
|
||||
|
||||
private static void CompilePrimeTesterSharesFactorKernel(Accelerator accelerator)
|
||||
{
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel(PrimeTesterSharesFactorKernel);
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterSharesFactorKernel);
|
||||
_ = kernel;
|
||||
}
|
||||
|
||||
private static KernelType _kernelType = KernelType.Pow2ModKernelScan | KernelType.OrderKernelScan | KernelType.IncrementalKernelScan | KernelType.IncrementalOrderKernelScan | KernelType.Pow2ModOrderKernelScan;
|
||||
private static void CompileGpuKernelPoolKernels(Accelerator accelerator)
|
||||
{
|
||||
private static void CompileGpuKernelPoolKernels(Accelerator accelerator)
|
||||
{
|
||||
_ = accelerator;
|
||||
var lease = GpuKernelPool.GetKernel(useGpuOrder: false);
|
||||
try
|
||||
{
|
||||
_ = lease.OrderKernel;
|
||||
_ = lease.IncrementalKernel;
|
||||
_ = lease.Pow2ModKernel;
|
||||
_ = lease.IncrementalOrderKernel;
|
||||
_ = lease.Pow2ModOrderKernel;
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
|
||||
GpuPrimeWorkLimiter.Acquire();
|
||||
int acceleratorIndex = AcceleratorPool.Shared.Rent();
|
||||
AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex);
|
||||
try
|
||||
{
|
||||
var kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, _kernelType);
|
||||
_ = kernels.Order;
|
||||
_ = kernels.Incremental;
|
||||
_ = kernels.Pow2Mod;
|
||||
_ = kernels.IncrementalOrder;
|
||||
_ = kernels.Pow2ModOrder;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Synchronize();
|
||||
AcceleratorStreamPool.Return(acceleratorIndex, stream);
|
||||
// GpuPrimeWorkLimiter.Release();
|
||||
}
|
||||
|
||||
stream = AcceleratorStreamPool.Rent(acceleratorIndex);
|
||||
GpuPrimeWorkLimiter.Acquire();
|
||||
try
|
||||
{
|
||||
var kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, _kernelType);
|
||||
_ = kernels.Order;
|
||||
_ = kernels.Incremental;
|
||||
_ = kernels.Pow2Mod;
|
||||
_ = kernels.IncrementalOrder;
|
||||
_ = kernels.Pow2ModOrder;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Synchronize();
|
||||
AcceleratorStreamPool.Return(acceleratorIndex, stream);
|
||||
// GpuPrimeWorkLimiter.Release();
|
||||
}
|
||||
}
|
||||
var orderLease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||
try
|
||||
{
|
||||
_ = orderLease.OrderKernel;
|
||||
_ = orderLease.IncrementalKernel;
|
||||
_ = orderLease.Pow2ModKernel;
|
||||
_ = orderLease.IncrementalOrderKernel;
|
||||
_ = orderLease.Pow2ModOrderKernel;
|
||||
}
|
||||
finally
|
||||
{
|
||||
orderLease.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,33 +18,22 @@ public class GpuPrimeLimiterTests
|
||||
var startedSecond = new TaskCompletionSource<bool>();
|
||||
var sw = new Stopwatch();
|
||||
|
||||
GpuPrimeWorkLimiter.Acquire();
|
||||
using var first = GpuPrimeWorkLimiter.Acquire();
|
||||
sw.Start();
|
||||
|
||||
var secondTask = Task.Run(() =>
|
||||
{
|
||||
GpuPrimeWorkLimiter.Acquire();
|
||||
try
|
||||
{
|
||||
startedSecond.TrySetResult(true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
GpuPrimeWorkLimiter.Release();
|
||||
}
|
||||
using var second = GpuPrimeWorkLimiter.Acquire();
|
||||
startedSecond.TrySetResult(true);
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
// Give secondTask a moment to attempt acquire (it should block).
|
||||
await Task.Delay(50);
|
||||
startedSecond.Task.IsCompleted.Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Release first after the initial wait so the second task can proceed.
|
||||
GpuPrimeWorkLimiter.Release();
|
||||
}
|
||||
// Give secondTask a moment to attempt acquire (it should block).
|
||||
await Task.Delay(50);
|
||||
startedSecond.Task.IsCompleted.Should().BeFalse();
|
||||
|
||||
// Release first after short delay, allowing second to continue.
|
||||
// (Dispose at scope end, but we want to end now.)
|
||||
first.Dispose();
|
||||
|
||||
await secondTask.WaitAsync(TimeSpan.FromSeconds(1));
|
||||
sw.Stop();
|
||||
|
||||
@@ -14,7 +14,7 @@ public class LucasLehmerPrimeTesterTests
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsMersennePrime_gpu_returns_expected_results(ulong p, bool expected)
|
||||
{
|
||||
new MersenneNumberLucasLehmerGpuTester().IsPrime(p, true).Should().Be(expected);
|
||||
new MersenneNumberLucasLehmerGpuTester().IsMersennePrime(p).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -26,7 +26,7 @@ public class LucasLehmerPrimeTesterTests
|
||||
const ulong exponent = 128UL;
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
tester.WarmUpNttParameters(exponent);
|
||||
tester.IsPrime(exponent, true).Should().BeFalse();
|
||||
tester.IsMersennePrime(exponent).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -49,7 +49,7 @@ public class LucasLehmerPrimeTesterTests
|
||||
{
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
tester.WarmUpNttParameters(exponent);
|
||||
tester.IsPrime(exponent, true).Should().BeFalse();
|
||||
tester.IsMersennePrime(exponent).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
||||
@@ -13,352 +13,367 @@ namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
[Collection("GpuNtt")]
|
||||
public class NttGpuMathTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ForwardGpu_reference_matches_cpu_forward()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ForwardGpu_reference_matches_cpu_forward()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ForwardGpu_staged_matches_cpu_forward()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ForwardGpu_staged_matches_cpu_forward()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Forward(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void InverseGpu_reference_matches_cpu_inverse()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[4];
|
||||
input[0] = new GpuUInt128(0UL, 5UL);
|
||||
input[1] = new GpuUInt128(0UL, 7UL);
|
||||
input[2] = new GpuUInt128(0UL, 9UL);
|
||||
input[3] = new GpuUInt128(0UL, 11UL);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[4];
|
||||
input.CopyTo(transformed);
|
||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(cpu);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(gpu);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void InverseGpu_reference_matches_cpu_inverse()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[4];
|
||||
input[0] = new GpuUInt128(0UL, 5UL);
|
||||
input[1] = new GpuUInt128(0UL, 7UL);
|
||||
input[2] = new GpuUInt128(0UL, 9UL);
|
||||
input[3] = new GpuUInt128(0UL, 11UL);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[4];
|
||||
input.CopyTo(transformed);
|
||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(cpu);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(gpu);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void InverseGpu_staged_matches_cpu_inverse()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[4];
|
||||
input[0] = new GpuUInt128(0UL, 5UL);
|
||||
input[1] = new GpuUInt128(0UL, 7UL);
|
||||
input[2] = new GpuUInt128(0UL, 9UL);
|
||||
input[3] = new GpuUInt128(0UL, 11UL);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[4];
|
||||
input.CopyTo(transformed);
|
||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(cpu);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(gpu);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void InverseGpu_staged_matches_cpu_inverse()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[4];
|
||||
input[0] = new GpuUInt128(0UL, 5UL);
|
||||
input[1] = new GpuUInt128(0UL, 7UL);
|
||||
input[2] = new GpuUInt128(0UL, 9UL);
|
||||
input[3] = new GpuUInt128(0UL, 11UL);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[4];
|
||||
input.CopyTo(transformed);
|
||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(cpu);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
transformed.CopyTo(gpu);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Montgomery_twiddle_inverses_multiply_to_r2()
|
||||
{
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Montgomery_twiddle_inverses_multiply_to_r2()
|
||||
{
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
|
||||
var accelerator = SharedGpuContext.CreateAccelerator();
|
||||
var stream = accelerator.CreateStream();
|
||||
GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
|
||||
try
|
||||
{
|
||||
var accelerator = gpu.Accelerator;
|
||||
|
||||
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
method.Should().NotBeNull();
|
||||
var cache = method!.Invoke(null, new object[] { accelerator, 8, modulus, primitiveRoot })!;
|
||||
var cacheType = cache.GetType();
|
||||
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
method.Should().NotBeNull();
|
||||
var cache = method!.Invoke(null, new object[] { accelerator, 8, modulus, primitiveRoot })!;
|
||||
var cacheType = cache.GetType();
|
||||
|
||||
var twiddlesMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesMont")!.GetValue(cache)!;
|
||||
var twiddlesInvMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesInvMont")!.GetValue(cache)!;
|
||||
var r2 = (ulong)cacheType.GetProperty("MontR2Mod64")!.GetValue(cache)!;
|
||||
var mod = (ulong)cacheType.GetProperty("ModulusLow")!.GetValue(cache)!;
|
||||
var twiddlesMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesMont")!.GetValue(cache)!;
|
||||
var twiddlesInvMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesInvMont")!.GetValue(cache)!;
|
||||
var r2 = (ulong)cacheType.GetProperty("MontR2Mod64")!.GetValue(cache)!;
|
||||
var mod = (ulong)cacheType.GetProperty("ModulusLow")!.GetValue(cache)!;
|
||||
|
||||
int count = 7; // length 8 -> 7 twiddles
|
||||
var forward = new GpuUInt128[count];
|
||||
var inverse = new GpuUInt128[count];
|
||||
twiddlesMont.CopyToCPU(stream, forward);
|
||||
twiddlesInvMont.CopyToCPU(stream, inverse);
|
||||
stream.Synchronize();
|
||||
stream.Dispose();
|
||||
accelerator.Dispose();
|
||||
int count = 7; // length 8 -> 7 twiddles
|
||||
var forward = new GpuUInt128[count];
|
||||
var inverse = new GpuUInt128[count];
|
||||
twiddlesMont.CopyToCPU(forward);
|
||||
twiddlesInvMont.CopyToCPU(inverse);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ulong prod = (ulong)((((BigInteger)forward[i].Low) * inverse[i].Low) % mod);
|
||||
prod.Should().Be(r2);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ulong prod = (ulong)((((BigInteger)forward[i].Low) * inverse[i].Low) % mod);
|
||||
prod.Should().Be(r2);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
gpu.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ConvolveGpu_reference_matches_cpu_convolve()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4];
|
||||
leftCpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
leftCpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
leftCpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
leftCpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
rightCpu[0] = new GpuUInt128(0UL, 5UL);
|
||||
rightCpu[1] = new GpuUInt128(0UL, 6UL);
|
||||
rightCpu[2] = new GpuUInt128(0UL, 7UL);
|
||||
rightCpu[3] = new GpuUInt128(0UL, 8UL);
|
||||
Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4];
|
||||
leftCpu.CopyTo(leftGpu);
|
||||
rightCpu.CopyTo(rightGpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < leftCpu.Length; i++)
|
||||
{
|
||||
leftGpu[i].Should().Be(leftCpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ConvolveGpu_reference_matches_cpu_convolve()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4];
|
||||
leftCpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
leftCpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
leftCpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
leftCpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
rightCpu[0] = new GpuUInt128(0UL, 5UL);
|
||||
rightCpu[1] = new GpuUInt128(0UL, 6UL);
|
||||
rightCpu[2] = new GpuUInt128(0UL, 7UL);
|
||||
rightCpu[3] = new GpuUInt128(0UL, 8UL);
|
||||
Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4];
|
||||
leftCpu.CopyTo(leftGpu);
|
||||
rightCpu.CopyTo(rightGpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < leftCpu.Length; i++)
|
||||
{
|
||||
leftGpu[i].Should().Be(leftCpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ConvolveGpu_staged_matches_cpu_convolve()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4];
|
||||
leftCpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
leftCpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
leftCpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
leftCpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
rightCpu[0] = new GpuUInt128(0UL, 5UL);
|
||||
rightCpu[1] = new GpuUInt128(0UL, 6UL);
|
||||
rightCpu[2] = new GpuUInt128(0UL, 7UL);
|
||||
rightCpu[3] = new GpuUInt128(0UL, 8UL);
|
||||
Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4];
|
||||
leftCpu.CopyTo(leftGpu);
|
||||
rightCpu.CopyTo(rightGpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < leftCpu.Length; i++)
|
||||
{
|
||||
leftGpu[i].Should().Be(leftCpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ConvolveGpu_staged_matches_cpu_convolve()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4];
|
||||
leftCpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
leftCpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
leftCpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
leftCpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
rightCpu[0] = new GpuUInt128(0UL, 5UL);
|
||||
rightCpu[1] = new GpuUInt128(0UL, 6UL);
|
||||
rightCpu[2] = new GpuUInt128(0UL, 7UL);
|
||||
rightCpu[3] = new GpuUInt128(0UL, 8UL);
|
||||
Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4];
|
||||
Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4];
|
||||
leftCpu.CopyTo(leftGpu);
|
||||
rightCpu.CopyTo(rightGpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot);
|
||||
NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < leftCpu.Length; i++)
|
||||
{
|
||||
leftGpu[i].Should().Be(leftCpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void SquareGpu_reference_matches_cpu_square()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void SquareGpu_reference_matches_cpu_square()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Reference;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void SquareGpu_staged_matches_cpu_square()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void SquareGpu_staged_matches_cpu_square()
|
||||
{
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
|
||||
cpu[0] = new GpuUInt128(0UL, 1UL);
|
||||
cpu[1] = new GpuUInt128(0UL, 2UL);
|
||||
cpu[2] = new GpuUInt128(0UL, 3UL);
|
||||
cpu[3] = new GpuUInt128(0UL, 4UL);
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
|
||||
cpu.CopyTo(gpu);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||
NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void BitReverseGpu_matches_cpu()
|
||||
{
|
||||
GpuUInt128[] cpu = new GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)cpu.Length; i++)
|
||||
{
|
||||
cpu[i] = new GpuUInt128(0UL, i);
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void BitReverseGpu_matches_cpu()
|
||||
{
|
||||
GpuUInt128[] cpu = new GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)cpu.Length; i++)
|
||||
{
|
||||
cpu[i] = new GpuUInt128(0UL, i);
|
||||
}
|
||||
|
||||
GpuUInt128[] gpu = (GpuUInt128[])cpu.Clone();
|
||||
NttGpuMath.BitReverse(cpu);
|
||||
NttGpuMath.BitReverseGpu(gpu);
|
||||
gpu.Should().Equal(cpu);
|
||||
}
|
||||
GpuUInt128[] gpu = (GpuUInt128[])cpu.Clone();
|
||||
NttGpuMath.BitReverse(cpu);
|
||||
NttGpuMath.BitReverseGpu(gpu);
|
||||
gpu.Should().Equal(cpu);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void DisposeAll_clears_ntt_caches()
|
||||
{
|
||||
// Ensure the cache starts empty.
|
||||
NttGpuMath.ClearCaches();
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void DisposeAll_clears_ntt_caches()
|
||||
{
|
||||
// Ensure the cache starts empty.
|
||||
NttGpuMath.ClearCaches();
|
||||
|
||||
Span<GpuUInt128> values = stackalloc GpuUInt128[2];
|
||||
values[0] = new GpuUInt128(0UL, 1UL);
|
||||
values[1] = new GpuUInt128(0UL, 2UL);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
Accelerator accelerator = SharedGpuContext.CreateAccelerator();
|
||||
var stream = accelerator.CreateStream();
|
||||
var buffer = accelerator.Allocate1D<GpuUInt128>(values.Length);
|
||||
buffer.View.CopyFromCPU(stream, ref values[0], values.Length);
|
||||
NttGpuMath.SquareDevice(accelerator, stream, buffer.View, modulus, primitiveRoot);
|
||||
stream.Synchronize();
|
||||
stream.Dispose();
|
||||
buffer.Dispose();
|
||||
accelerator.Dispose();
|
||||
Span<GpuUInt128> values = stackalloc GpuUInt128[2];
|
||||
values[0] = new GpuUInt128(0UL, 1UL);
|
||||
values[1] = new GpuUInt128(0UL, 2UL);
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
|
||||
try
|
||||
{
|
||||
var buffer = gpu.Accelerator.Allocate1D<GpuUInt128>(values.Length);
|
||||
try
|
||||
{
|
||||
buffer.View.CopyFromCPU(ref values[0], values.Length);
|
||||
NttGpuMath.SquareDevice(gpu.Accelerator, buffer.View, modulus, primitiveRoot);
|
||||
}
|
||||
finally
|
||||
{
|
||||
buffer.Dispose();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
gpu.Dispose();
|
||||
}
|
||||
|
||||
// Verify that the square cache now holds entries for the accelerator.
|
||||
var field = typeof(NttGpuMath).GetField("SquareCache", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var cacheObj = field.GetValue(null)!;
|
||||
int count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
|
||||
count.Should().BeGreaterThan(0);
|
||||
// Verify that the square cache now holds entries for the accelerator.
|
||||
var field = typeof(NttGpuMath).GetField("SquareCache", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
var cacheObj = field.GetValue(null)!;
|
||||
int count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
|
||||
count.Should().BeGreaterThan(0);
|
||||
|
||||
count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
|
||||
count.Should().Be(0);
|
||||
}
|
||||
GpuContextPool.DisposeAll();
|
||||
|
||||
count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
|
||||
count.Should().Be(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,83 +9,86 @@ namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
[Trait("Category", "Fast")]
|
||||
public class NttMontgomeryTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void SquareDevice_montgomery64_matches_cpu_square_on_small_input()
|
||||
{
|
||||
// Small 64-bit modulus and tiny input to keep runtime trivial.
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void SquareDevice_montgomery64_matches_cpu_square_on_small_input()
|
||||
{
|
||||
// Small 64-bit modulus and tiny input to keep runtime trivial.
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)cpu.Length; i++)
|
||||
{
|
||||
cpu[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
||||
}
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[16];
|
||||
cpu.CopyTo(gpu);
|
||||
for (int i = cpu.Length; i < gpu.Length; i++)
|
||||
{
|
||||
gpu[i] = new GpuUInt128(0UL, 0UL);
|
||||
}
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)cpu.Length; i++)
|
||||
{
|
||||
cpu[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
||||
}
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[16];
|
||||
cpu.CopyTo(gpu);
|
||||
for (int i = cpu.Length; i < gpu.Length; i++)
|
||||
{
|
||||
gpu[i] = new GpuUInt128(0UL, 0UL);
|
||||
}
|
||||
|
||||
// CPU reference
|
||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||
// CPU reference
|
||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||
|
||||
// Device execution
|
||||
var accelerator = SharedGpuContext.CreateAccelerator();
|
||||
var stream = accelerator.CreateStream();
|
||||
using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
|
||||
buffer.View.CopyFromCPU(stream, ref gpu[0], gpu.Length);
|
||||
NttGpuMath.SquareDevice(accelerator, stream, buffer.View, modulus, primitiveRoot);
|
||||
buffer.View.CopyToCPU(stream, ref gpu[0], gpu.Length);
|
||||
stream.Synchronize();
|
||||
stream.Dispose();
|
||||
accelerator.Dispose();
|
||||
// Device execution
|
||||
GpuContextPool.GpuContextLease context = GpuContextPool.Rent();
|
||||
try
|
||||
{
|
||||
var accelerator = context.Accelerator;
|
||||
using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
|
||||
buffer.View.CopyFromCPU(ref gpu[0], gpu.Length);
|
||||
NttGpuMath.SquareDevice(accelerator, buffer.View, modulus, primitiveRoot);
|
||||
buffer.View.CopyToCPU(ref gpu[0], gpu.Length);
|
||||
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
for (int i = 0; i < cpu.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
context.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void InverseDevice_montgomery64_matches_cpu_inverse_on_small_input()
|
||||
{
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void InverseDevice_montgomery64_matches_cpu_inverse_on_small_input()
|
||||
{
|
||||
var modulus = new GpuUInt128(0UL, 17UL);
|
||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)input.Length; i++)
|
||||
{
|
||||
input[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
||||
}
|
||||
|
||||
Span<GpuUInt128> input = stackalloc GpuUInt128[8];
|
||||
for (ulong i = 0; i < (ulong)input.Length; i++)
|
||||
{
|
||||
input[(int)i] = new GpuUInt128(0UL, i + 1UL);
|
||||
}
|
||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[input.Length];
|
||||
input.CopyTo(transformed);
|
||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||
|
||||
Span<GpuUInt128> transformed = stackalloc GpuUInt128[input.Length];
|
||||
input.CopyTo(transformed);
|
||||
NttGpuMath.Forward(transformed, modulus, primitiveRoot);
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[input.Length];
|
||||
transformed.CopyTo(cpu);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
|
||||
Span<GpuUInt128> cpu = stackalloc GpuUInt128[input.Length];
|
||||
transformed.CopyTo(cpu);
|
||||
NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
|
||||
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[input.Length];
|
||||
transformed.CopyTo(gpu);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
var original = NttGpuMath.GpuTransformBackend;
|
||||
try
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
|
||||
Span<GpuUInt128> gpu = stackalloc GpuUInt128[input.Length];
|
||||
transformed.CopyTo(gpu);
|
||||
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
gpu[i].Should().Be(cpu[i]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NttGpuMath.GpuTransformBackend = original;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
@@ -9,23 +10,41 @@ public class PrimeTesterGpuTests
|
||||
[Fact]
|
||||
public void IsPrimeGpu_accepts_known_primes()
|
||||
{
|
||||
var tester = new PrimeTester();
|
||||
ulong[] primes = [31UL, 61UL, 89UL, 107UL, 127UL, 521UL];
|
||||
foreach (ulong prime in primes)
|
||||
{
|
||||
PrimeTester.IsPrimeGpu(prime).Should().BeTrue();
|
||||
}
|
||||
GpuContextPool.ForceCpu = false;
|
||||
try
|
||||
{
|
||||
var tester = new PrimeTester();
|
||||
ulong[] primes = [31UL, 61UL, 89UL, 107UL, 127UL, 521UL];
|
||||
foreach (ulong prime in primes)
|
||||
{
|
||||
tester.IsPrimeGpu(prime, CancellationToken.None).Should().BeTrue();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
GpuContextPool.ForceCpu = false;
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsPrimeGpu_rejects_composites()
|
||||
{
|
||||
var tester = new PrimeTester();
|
||||
ulong[] composites = [33UL, 39UL, 51UL, 77UL, 91UL];
|
||||
foreach (ulong composite in composites)
|
||||
{
|
||||
PrimeTester.IsPrimeGpu(composite).Should().BeFalse();
|
||||
}
|
||||
GpuContextPool.ForceCpu = false;
|
||||
try
|
||||
{
|
||||
var tester = new PrimeTester();
|
||||
ulong[] composites = [33UL, 39UL, 51UL, 77UL, 91UL];
|
||||
foreach (ulong composite in composites)
|
||||
{
|
||||
tester.IsPrimeGpu(composite, CancellationToken.None).Should().BeFalse();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
GpuContextPool.ForceCpu = false;
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
|
||||
@@ -7,26 +7,26 @@ namespace PerfectNumbers.Core.Tests;
|
||||
[Collection("GpuNtt")]
|
||||
public class GpuKernelPoolTests
|
||||
{
|
||||
// [Fact]
|
||||
// [Trait("Category", "Fast")]
|
||||
// public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls()
|
||||
// {
|
||||
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
// try
|
||||
// {
|
||||
// var accelerator = lease.Accelerator;
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls()
|
||||
{
|
||||
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
try
|
||||
{
|
||||
var accelerator = lease.Accelerator;
|
||||
|
||||
// var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||
// var second = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||
var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||
var second = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||
|
||||
// second.Equals(first).Should().BeTrue();
|
||||
// second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1);
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// lease.Dispose();
|
||||
// }
|
||||
// }
|
||||
second.Equals(first).Should().BeTrue();
|
||||
second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
|
||||
@@ -17,31 +17,19 @@ public class GpuWorkLimiterTests
|
||||
var startedSecond = new TaskCompletionSource<bool>();
|
||||
var sw = new Stopwatch();
|
||||
|
||||
var first = GpuWorkLimiter.Acquire();
|
||||
using var first = GpuWorkLimiter.Acquire();
|
||||
sw.Start();
|
||||
|
||||
var secondTask = Task.Run(() =>
|
||||
{
|
||||
var second = GpuWorkLimiter.Acquire();
|
||||
try
|
||||
{
|
||||
startedSecond.TrySetResult(true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
second.Dispose();
|
||||
}
|
||||
using var second = GpuWorkLimiter.Acquire();
|
||||
startedSecond.TrySetResult(true);
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(50);
|
||||
startedSecond.Task.IsCompleted.Should().BeFalse();
|
||||
}
|
||||
finally
|
||||
{
|
||||
first.Dispose();
|
||||
}
|
||||
await Task.Delay(50);
|
||||
startedSecond.Task.IsCompleted.Should().BeFalse();
|
||||
|
||||
first.Dispose();
|
||||
|
||||
await secondTask.WaitAsync(TimeSpan.FromSeconds(1));
|
||||
sw.Stop();
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class HeuristicCombinedPrimeTesterTests
|
||||
{
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData((byte)1)]
|
||||
[InlineData((byte)7)]
|
||||
[InlineData((byte)9)]
|
||||
public void Enumerator_uses_one_a_one_b_schedule_by_default(byte lastDigit)
|
||||
{
|
||||
Span<HeuristicCombinedPrimeTester.HeuristicGroupBSequenceState> groupBState =
|
||||
stackalloc HeuristicCombinedPrimeTester.HeuristicGroupBSequenceState[4];
|
||||
|
||||
var enumerator = HeuristicCombinedPrimeTester.CreateHeuristicDivisorEnumerator(500UL * 500UL, lastDigit, groupBState);
|
||||
var results = new List<HeuristicCombinedPrimeTester.HeuristicDivisorCandidate>(32);
|
||||
|
||||
while (results.Count < 20 && enumerator.TryGetNext(out var candidate))
|
||||
{
|
||||
results.Add(candidate);
|
||||
}
|
||||
|
||||
const int segmentsToCheck = 6;
|
||||
results.Count.Should().BeGreaterThanOrEqualTo(4 + segmentsToCheck * 2);
|
||||
results[0].Value.Should().Be(3UL);
|
||||
results[0].Group.Should().Be(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupAConstant);
|
||||
results[1].Value.Should().Be(7UL);
|
||||
results[1].Group.Should().Be(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupAConstant);
|
||||
results[2].Value.Should().Be(11UL);
|
||||
results[2].Group.Should().Be(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupAConstant);
|
||||
results[3].Value.Should().Be(13UL);
|
||||
results[3].Group.Should().Be(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupAConstant);
|
||||
|
||||
int index = 4;
|
||||
|
||||
for (int segment = 0; segment < segmentsToCheck; segment++)
|
||||
{
|
||||
HeuristicCombinedPrimeTester.HeuristicDivisorCandidate groupACandidate = results[index++];
|
||||
groupACandidate.Value.Should().BeGreaterThan(13UL);
|
||||
(groupACandidate.Value % 10UL).Should().Be(3UL);
|
||||
groupACandidate.Group.Should().NotBe(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupB);
|
||||
|
||||
HeuristicCombinedPrimeTester.HeuristicDivisorCandidate groupBCandidate = results[index++];
|
||||
groupBCandidate.Value.Should().BeGreaterThan(13UL);
|
||||
(groupBCandidate.Value % 10UL).Should().NotBe(3UL);
|
||||
groupBCandidate.Group.Should().Be(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupB);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData((byte)1)]
|
||||
[InlineData((byte)7)]
|
||||
[InlineData((byte)9)]
|
||||
public void One_a_one_b_pattern_interleaves_group_a_and_group_b_values(byte lastDigit)
|
||||
{
|
||||
ReadOnlySpan<ulong> combined = HeuristicCombinedPrimeTester.GetCombinedDivisors(
|
||||
lastDigit,
|
||||
HeuristicCombinedPrimeTester.CombinedDivisorPattern.OneAOneB);
|
||||
|
||||
combined.Length.Should().BeGreaterThan(18);
|
||||
combined[0].Should().Be(3UL);
|
||||
combined[1].Should().Be(7UL);
|
||||
combined[2].Should().Be(11UL);
|
||||
combined[3].Should().Be(13UL);
|
||||
|
||||
int index = 4;
|
||||
const int segmentsToCheck = 6;
|
||||
|
||||
for (int segment = 0; segment < segmentsToCheck; segment++)
|
||||
{
|
||||
ulong groupAValue = combined[index++];
|
||||
groupAValue.Should().BeGreaterThan(13UL);
|
||||
(groupAValue % 10UL).Should().Be(3UL);
|
||||
|
||||
ulong groupBValue = combined[index++];
|
||||
groupBValue.Should().BeGreaterThan(13UL);
|
||||
(groupBValue % 10UL).Should().NotBe(3UL);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData((byte)1)]
|
||||
[InlineData((byte)7)]
|
||||
[InlineData((byte)9)]
|
||||
public void Two_a_one_b_pattern_interleaves_two_group_a_values_before_group_b(byte lastDigit)
|
||||
{
|
||||
ReadOnlySpan<ulong> combined = HeuristicCombinedPrimeTester.GetCombinedDivisors(
|
||||
lastDigit,
|
||||
HeuristicCombinedPrimeTester.CombinedDivisorPattern.TwoAOneB);
|
||||
|
||||
combined.Length.Should().BeGreaterThan(20);
|
||||
combined[0].Should().Be(3UL);
|
||||
combined[1].Should().Be(7UL);
|
||||
combined[2].Should().Be(11UL);
|
||||
combined[3].Should().Be(13UL);
|
||||
|
||||
int index = 4;
|
||||
const int segmentsToCheck = 5;
|
||||
|
||||
for (int segment = 0; segment < segmentsToCheck; segment++)
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
ulong groupAValue = combined[index++];
|
||||
groupAValue.Should().BeGreaterThan(13UL);
|
||||
(groupAValue % 10UL).Should().Be(3UL);
|
||||
}
|
||||
|
||||
ulong groupBValue = combined[index++];
|
||||
groupBValue.Should().BeGreaterThan(13UL);
|
||||
(groupBValue % 10UL).Should().NotBe(3UL);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData((byte)1)]
|
||||
[InlineData((byte)7)]
|
||||
[InlineData((byte)9)]
|
||||
public void Three_a_two_b_pattern_interleaves_three_group_a_values_before_two_group_b_values(byte lastDigit)
|
||||
{
|
||||
ReadOnlySpan<ulong> combined = HeuristicCombinedPrimeTester.GetCombinedDivisors(
|
||||
lastDigit,
|
||||
HeuristicCombinedPrimeTester.CombinedDivisorPattern.ThreeATwoB);
|
||||
|
||||
combined.Length.Should().BeGreaterThan(28);
|
||||
combined[0].Should().Be(3UL);
|
||||
combined[1].Should().Be(7UL);
|
||||
combined[2].Should().Be(11UL);
|
||||
combined[3].Should().Be(13UL);
|
||||
|
||||
int index = 4;
|
||||
const int segmentsToCheck = 4;
|
||||
|
||||
for (int segment = 0; segment < segmentsToCheck; segment++)
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
ulong groupAValue = combined[index++];
|
||||
groupAValue.Should().BeGreaterThan(13UL);
|
||||
(groupAValue % 10UL).Should().Be(3UL);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
ulong groupBValue = combined[index++];
|
||||
groupBValue.Should().BeGreaterThan(13UL);
|
||||
(groupBValue % 10UL).Should().NotBe(3UL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData((byte)1)]
|
||||
[InlineData((byte)7)]
|
||||
[InlineData((byte)9)]
|
||||
public void Three_a_one_b_pattern_interleaves_three_group_a_values_before_group_b(byte lastDigit)
|
||||
{
|
||||
ReadOnlySpan<ulong> combined = HeuristicCombinedPrimeTester.GetCombinedDivisors(
|
||||
lastDigit,
|
||||
HeuristicCombinedPrimeTester.CombinedDivisorPattern.ThreeAOneB);
|
||||
|
||||
combined.Length.Should().BeGreaterThan(24);
|
||||
combined[0].Should().Be(3UL);
|
||||
combined[1].Should().Be(7UL);
|
||||
combined[2].Should().Be(11UL);
|
||||
combined[3].Should().Be(13UL);
|
||||
|
||||
int index = 4;
|
||||
const int segmentsToCheck = 4;
|
||||
|
||||
for (int segment = 0; segment < segmentsToCheck; segment++)
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
ulong groupAValue = combined[index++];
|
||||
groupAValue.Should().BeGreaterThan(13UL);
|
||||
(groupAValue % 10UL).Should().Be(3UL);
|
||||
}
|
||||
|
||||
ulong groupBValue = combined[index++];
|
||||
groupBValue.Should().BeGreaterThan(13UL);
|
||||
(groupBValue % 10UL).Should().NotBe(3UL);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(0UL)]
|
||||
[InlineData(1UL)]
|
||||
[InlineData(2UL)]
|
||||
[InlineData(3UL)]
|
||||
[InlineData(4UL)]
|
||||
[InlineData(5UL)]
|
||||
[InlineData(8UL)]
|
||||
[InlineData(9UL)]
|
||||
[InlineData(15UL)]
|
||||
[InlineData(16UL)]
|
||||
[InlineData(17UL)]
|
||||
[InlineData(24UL)]
|
||||
[InlineData(25UL)]
|
||||
[InlineData(26UL)]
|
||||
[InlineData(255UL)]
|
||||
[InlineData(256UL)]
|
||||
[InlineData(257UL)]
|
||||
[InlineData(1_000UL)]
|
||||
[InlineData(1_048_575UL)]
|
||||
[InlineData(1_048_576UL)]
|
||||
[InlineData(1_048_577UL)]
|
||||
[InlineData(4_000_000UL)]
|
||||
[InlineData(4_000_001UL)]
|
||||
[InlineData(4_000_004UL)]
|
||||
[InlineData(65_535_999UL)]
|
||||
[InlineData(65_536_000UL)]
|
||||
[InlineData(65_536_001UL)]
|
||||
[InlineData(4_294_967_295UL)]
|
||||
[InlineData(4_294_967_296UL)]
|
||||
[InlineData(4_294_967_297UL)]
|
||||
[InlineData(18_446_744_073_709_551_615UL)]
|
||||
[InlineData(18_446_744_073_709_551_614UL)]
|
||||
[InlineData(18_446_744_073_709_551_613UL)]
|
||||
public void ComputeHeuristicDivisorSquareLimit_returns_input_value(ulong value)
|
||||
{
|
||||
ulong limit = HeuristicCombinedPrimeTester.ComputeHeuristicDivisorSquareLimit(value);
|
||||
|
||||
limit.Should().Be(value);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
||||
{
|
||||
var candidates = new List<ulong> { 5UL, 7UL, 11UL, 13UL };
|
||||
var tester = new FakeTester();
|
||||
var results = new List<(ulong Prime, bool Searched, bool Detailed, bool Passed, ulong Divisor)>();
|
||||
var results = new List<(ulong Prime, bool Searched, bool Detailed, bool Passed)>();
|
||||
int compositeMarks = 0;
|
||||
int clearedMarks = 0;
|
||||
|
||||
@@ -22,7 +22,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
||||
startPrime: 0UL,
|
||||
markComposite: () => compositeMarks++,
|
||||
clearComposite: () => clearedMarks++,
|
||||
printResult: (prime, searched, detailed, passed, divisor) => results.Add((prime, searched, detailed, passed, divisor)),
|
||||
printResult: (prime, searched, detailed, passed) => results.Add((prime, searched, detailed, passed)),
|
||||
threadCount: 1,
|
||||
primesPerTask: 2);
|
||||
|
||||
@@ -32,10 +32,10 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
||||
results.Should().Equal(
|
||||
new[]
|
||||
{
|
||||
(5UL, true, true, true, 0UL),
|
||||
(7UL, true, true, false, 7UL),
|
||||
(11UL, true, true, false, 11UL),
|
||||
(13UL, true, true, true, 0UL),
|
||||
(5UL, true, true, true),
|
||||
(7UL, true, true, false),
|
||||
(11UL, true, true, false),
|
||||
(13UL, true, true, true),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,23 +57,10 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
||||
return DivisorLimit;
|
||||
}
|
||||
|
||||
public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor)
|
||||
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
if (prime % 7UL == 0UL)
|
||||
{
|
||||
divisor = 7UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prime % 11UL == 0UL)
|
||||
{
|
||||
divisor = 11UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
divisor = 0UL;
|
||||
return true;
|
||||
return prime % 7UL != 0UL && prime % 11UL != 0UL;
|
||||
}
|
||||
|
||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
||||
@@ -96,7 +83,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void Return()
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,17 +13,14 @@ public class MersenneNumberDivisorCpuTesterTests
|
||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||
tester.ConfigureFromMaxPrime(11UL);
|
||||
|
||||
tester.IsPrime(5UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue();
|
||||
tester.IsPrime(5UL, out bool divisorsExhausted).Should().BeTrue();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
divisor.Should().Be(0UL);
|
||||
|
||||
tester.IsPrime(7UL, out divisorsExhausted, out divisor).Should().BeTrue();
|
||||
tester.IsPrime(7UL, out divisorsExhausted).Should().BeTrue();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
divisor.Should().Be(0UL);
|
||||
|
||||
tester.IsPrime(11UL, out divisorsExhausted, out divisor).Should().BeFalse();
|
||||
tester.IsPrime(11UL, out divisorsExhausted).Should().BeFalse();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
divisor.Should().BeGreaterThan(0UL);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -33,25 +30,18 @@ public class MersenneNumberDivisorCpuTesterTests
|
||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||
tester.ConfigureFromMaxPrime(13UL);
|
||||
|
||||
var session = tester.CreateDivisorSession();
|
||||
try
|
||||
{
|
||||
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL, MontgomeryDivisorData.FromModulus(23UL));
|
||||
session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL, MontgomeryDivisorData.FromModulus(23UL));
|
||||
session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||
|
||||
Array.Fill(hits, (byte)0);
|
||||
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL, MontgomeryDivisorData.FromModulus(31UL));
|
||||
session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
|
||||
}
|
||||
finally
|
||||
{
|
||||
session.Return();
|
||||
}
|
||||
Array.Fill(hits, (byte)0);
|
||||
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL, MontgomeryDivisorData.FromModulus(31UL));
|
||||
session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -61,22 +51,15 @@ public class MersenneNumberDivisorCpuTesterTests
|
||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
|
||||
var session = tester.CreateDivisorSession();
|
||||
try
|
||||
{
|
||||
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
|
||||
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
|
||||
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
||||
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
finally
|
||||
{
|
||||
session.Return();
|
||||
}
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -86,22 +69,15 @@ public class MersenneNumberDivisorCpuTesterTests
|
||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||
tester.ConfigureFromMaxPrime(61UL);
|
||||
|
||||
var session = tester.CreateDivisorSession();
|
||||
try
|
||||
{
|
||||
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
|
||||
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
|
||||
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
|
||||
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
finally
|
||||
{
|
||||
session.Return();
|
||||
}
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,17 +73,14 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
|
||||
tester.IsPrime(31UL, out bool exhausted, out ulong divisor).Should().BeTrue();
|
||||
tester.IsPrime(31UL, out bool exhausted).Should().BeTrue();
|
||||
exhausted.Should().BeTrue();
|
||||
divisor.Should().Be(0UL);
|
||||
|
||||
tester.IsPrime(37UL, out exhausted, out divisor).Should().BeFalse();
|
||||
tester.IsPrime(37UL, out exhausted).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
divisor.Should().BeGreaterThan(0UL);
|
||||
|
||||
tester.IsPrime(43UL, out exhausted, out divisor).Should().BeFalse();
|
||||
tester.IsPrime(43UL, out exhausted).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
divisor.Should().BeGreaterThan(0UL);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -108,15 +105,12 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(41UL);
|
||||
|
||||
tester.IsPrime(31UL, out _, out ulong divisor).Should().BeTrue();
|
||||
divisor.Should().Be(0UL);
|
||||
tester.IsPrime(37UL, out bool exhausted, out divisor).Should().BeFalse();
|
||||
tester.IsPrime(31UL, out _).Should().BeTrue();
|
||||
tester.IsPrime(37UL, out bool exhausted).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
divisor.Should().BeGreaterThan(0UL);
|
||||
|
||||
tester.IsPrime(41UL, out bool divisorsExhausted, out divisor).Should().BeFalse();
|
||||
tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
divisor.Should().BeGreaterThan(0UL);
|
||||
}
|
||||
|
||||
|
||||
@@ -130,16 +124,8 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(exponent);
|
||||
|
||||
tester.IsPrime(exponent, out bool exhausted, out ulong divisor).Should().Be(expectedPrime);
|
||||
tester.IsPrime(exponent, out bool exhausted).Should().Be(expectedPrime);
|
||||
exhausted.Should().BeTrue();
|
||||
if (expectedPrime)
|
||||
{
|
||||
divisor.Should().Be(0UL);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisor.Should().BeGreaterThan(0UL);
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
@@ -152,9 +138,8 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
|
||||
tester.IsPrime(41UL, out bool divisorsExhausted, out ulong divisor).Should().BeFalse();
|
||||
tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
divisor.Should().BeGreaterThan(0UL);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -164,20 +149,13 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
|
||||
var session = tester.CreateDivisorSession();
|
||||
try
|
||||
{
|
||||
ulong[] primes = { 31UL, 37UL, 41UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 31UL, 37UL, 41UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), 0UL, primes, hits);
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), 0UL, primes, hits);
|
||||
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0 });
|
||||
}
|
||||
finally
|
||||
{
|
||||
session.Return();
|
||||
}
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -187,25 +165,18 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
|
||||
var session = tester.CreateDivisorSession();
|
||||
try
|
||||
{
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0 });
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0 });
|
||||
|
||||
Array.Fill(hits, (byte)0);
|
||||
ulong cycle13367 = MersenneDivisorCycles.CalculateCycleLength(13367UL, MontgomeryDivisorData.FromModulus(13367UL));
|
||||
session.CheckDivisor(13367UL, MontgomeryDivisorData.FromModulus(13367UL), cycle13367, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||
}
|
||||
finally
|
||||
{
|
||||
session.Return();
|
||||
}
|
||||
Array.Fill(hits, (byte)0);
|
||||
ulong cycle13367 = MersenneDivisorCycles.CalculateCycleLength(13367UL, MontgomeryDivisorData.FromModulus(13367UL));
|
||||
session.CheckDivisor(13367UL, MontgomeryDivisorData.FromModulus(13367UL), cycle13367, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -219,21 +190,14 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
|
||||
tester.ConfigureFromMaxPrime(47UL);
|
||||
|
||||
var session = tester.CreateDivisorSession();
|
||||
try
|
||||
{
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
|
||||
}
|
||||
finally
|
||||
{
|
||||
session.Return();
|
||||
}
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -246,23 +210,16 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
};
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
|
||||
var session = tester.CreateDivisorSession();
|
||||
try
|
||||
{
|
||||
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
|
||||
|
||||
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
||||
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
||||
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
finally
|
||||
{
|
||||
session.Return();
|
||||
}
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -275,23 +232,16 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
};
|
||||
tester.ConfigureFromMaxPrime(61UL);
|
||||
|
||||
var session = tester.CreateDivisorSession();
|
||||
try
|
||||
{
|
||||
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
|
||||
|
||||
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
|
||||
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
|
||||
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
finally
|
||||
{
|
||||
session.Return();
|
||||
}
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -324,15 +274,15 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
.GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)!
|
||||
.SetValue(tester, 200UL);
|
||||
|
||||
tester.IsPrime(19UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue();
|
||||
tester.IsPrime(19UL, out bool divisorsExhausted).Should().BeTrue();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
divisor.Should().Be(0UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
tableField.SetValue(cycles, originalTable);
|
||||
smallCyclesField.SetValue(cycles, originalSmall);
|
||||
DivisorCycleCache.Shared.RefreshSnapshot();
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,21 +297,14 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
|
||||
tester.ConfigureFromMaxPrime(47UL);
|
||||
|
||||
var session = tester.CreateDivisorSession();
|
||||
try
|
||||
{
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
|
||||
}
|
||||
finally
|
||||
{
|
||||
session.Return();
|
||||
}
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Cpu;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Cpu;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Reflection;
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
@@ -54,6 +55,7 @@ public class MersenneNumberOrderGpuTesterTests
|
||||
ulong[]? backupSmall = originalSmall is null ? null : (ulong[])originalSmall.Clone();
|
||||
|
||||
cycles.LoadFrom(tempPath);
|
||||
GpuContextPool.DisposeAll();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -73,6 +75,7 @@ public class MersenneNumberOrderGpuTesterTests
|
||||
{
|
||||
tableField.SetValue(cycles, originalTable);
|
||||
smallCyclesField.SetValue(cycles, backupSmall);
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Cpu;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
@@ -17,29 +17,12 @@ public class PrimeTesterIsPrimeTests
|
||||
[InlineData(97UL, true)]
|
||||
[InlineData(133UL, false)]
|
||||
[InlineData(137UL, true)]
|
||||
[InlineData(143UL, false)]
|
||||
[InlineData(199UL, true)]
|
||||
[InlineData(209UL, false)]
|
||||
[InlineData(403UL, false)]
|
||||
[InlineData(341UL, false)]
|
||||
public void IsPrime_returns_expected_results(ulong n, bool expected)
|
||||
{
|
||||
var tester = new PrimeTester();
|
||||
|
||||
PrimeTester.IsPrime(n).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[InlineData(139UL, true)]
|
||||
[InlineData(189UL, false)]
|
||||
[InlineData(193UL, true)]
|
||||
[InlineData(279UL, false)]
|
||||
public void IsPrimeGpu_uses_residue_specific_tables(ulong n, bool expected)
|
||||
{
|
||||
var tester = new PrimeTester();
|
||||
|
||||
PrimeTester.IsPrimeGpu(n).Should().Be(expected);
|
||||
tester.IsPrime(n, CancellationToken.None).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -47,14 +30,20 @@ public class PrimeTesterIsPrimeTests
|
||||
public void IsPrimeGpu_falls_back_to_cpu_when_forced()
|
||||
{
|
||||
var tester = new PrimeTester();
|
||||
PrimeTester.IsPrime(137UL).Should().BeTrue();
|
||||
PrimeTester.IsPrime(341UL).Should().BeFalse();
|
||||
GpuContextPool.ForceCpu = true;
|
||||
|
||||
tester.IsPrimeGpu(137UL, CancellationToken.None).Should().BeTrue();
|
||||
|
||||
tester.IsPrimeGpu(341UL, CancellationToken.None).Should().BeFalse();
|
||||
|
||||
GpuContextPool.ForceCpu = false;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsPrimeGpu_handles_parallel_calls_for_known_primes()
|
||||
{
|
||||
GpuContextPool.ForceCpu = false;
|
||||
GpuPrimeWorkLimiter.SetLimit(64);
|
||||
PrimeTester.GpuBatchSize = 256_000;
|
||||
|
||||
@@ -64,7 +53,7 @@ public class PrimeTesterIsPrimeTests
|
||||
var testers = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true);
|
||||
Parallel.For(0, primes.Length, i =>
|
||||
{
|
||||
results[i] = PrimeTester.IsPrimeGpu(primes[i]);
|
||||
results[i] = testers.Value!.IsPrimeGpu(primes[i], CancellationToken.None);
|
||||
});
|
||||
|
||||
results.Should().AllBeEquivalentTo(true);
|
||||
@@ -73,7 +62,7 @@ public class PrimeTesterIsPrimeTests
|
||||
{
|
||||
foreach (ulong prime in primes)
|
||||
{
|
||||
PrimeTester.IsPrimeGpu(prime).Should().BeTrue();
|
||||
testers.Value!.IsPrimeGpu(prime, CancellationToken.None).Should().BeTrue();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,39 +32,14 @@ public class PrimesGeneratorTests
|
||||
var lastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne;
|
||||
var lastSeven = PrimesGenerator.SmallPrimesLastSeven;
|
||||
var lastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven;
|
||||
var lastThree = DivisorGenerator.SmallPrimesLastThree;
|
||||
var lastThreePow2 = DivisorGenerator.SmallPrimesPow2LastThree;
|
||||
var lastNine = DivisorGenerator.SmallPrimesLastNine;
|
||||
var lastNinePow2 = DivisorGenerator.SmallPrimesPow2LastNine;
|
||||
var lastOneWithoutThree = DivisorGenerator.SmallPrimesLastOneWithoutLastThree;
|
||||
var lastOneWithoutThreePow2 = DivisorGenerator.SmallPrimesPow2LastOneWithoutLastThree;
|
||||
var lastSevenWithoutThree = DivisorGenerator.SmallPrimesLastSevenWithoutLastThree;
|
||||
var lastSevenWithoutThreePow2 = DivisorGenerator.SmallPrimesPow2LastSevenWithoutLastThree;
|
||||
var lastNineWithoutThree = DivisorGenerator.SmallPrimesLastNineWithoutLastThree;
|
||||
var lastNineWithoutThreePow2 = DivisorGenerator.SmallPrimesPow2LastNineWithoutLastThree;
|
||||
|
||||
lastOne.Length.Should().Be((int)PerfectNumberConstants.PrimesLimit);
|
||||
lastSeven.Length.Should().Be((int)PerfectNumberConstants.PrimesLimit);
|
||||
lastThree.Length.Should().Be((int)PerfectNumberConstants.PrimesLimit);
|
||||
lastNine.Length.Should().Be((int)PerfectNumberConstants.PrimesLimit);
|
||||
lastOneWithoutThree.Length.Should().BeGreaterThan(0);
|
||||
lastSevenWithoutThree.Length.Should().BeGreaterThan(0);
|
||||
lastNineWithoutThree.Length.Should().BeGreaterThan(0);
|
||||
lastOneWithoutThree.Length.Should().BeLessThan(lastOne.Length);
|
||||
lastSevenWithoutThree.Length.Should().BeLessThan(lastSeven.Length);
|
||||
lastNineWithoutThree.Length.Should().BeLessThan(lastNine.Length);
|
||||
lastOnePow2.Length.Should().Be(lastOne.Length);
|
||||
lastSevenPow2.Length.Should().Be(lastSeven.Length);
|
||||
lastThreePow2.Length.Should().Be(lastThree.Length);
|
||||
lastNinePow2.Length.Should().Be(lastNine.Length);
|
||||
lastOneWithoutThreePow2.Length.Should().Be(lastOneWithoutThree.Length);
|
||||
lastSevenWithoutThreePow2.Length.Should().Be(lastSevenWithoutThree.Length);
|
||||
lastNineWithoutThreePow2.Length.Should().Be(lastNineWithoutThree.Length);
|
||||
|
||||
lastOne[..5].Should().Equal([3U, 7U, 11U, 13U, 19U]);
|
||||
lastSeven[..5].Should().Equal([3U, 7U, 11U, 13U, 17U]);
|
||||
lastThree[..6].Should().Equal([3U, 7U, 11U, 13U, 17U, 19U]);
|
||||
lastNine[..6].Should().Equal([3U, 7U, 11U, 13U, 17U, 19U]);
|
||||
|
||||
for (int i = 0; i < lastOne.Length; i++)
|
||||
{
|
||||
@@ -83,64 +58,6 @@ public class PrimesGeneratorTests
|
||||
.Should().BeTrue();
|
||||
lastSevenPow2[i].Should().Be(prime * (ulong)prime);
|
||||
}
|
||||
|
||||
for (int i = 0; i < lastThree.Length; i++)
|
||||
{
|
||||
uint prime = lastThree[i];
|
||||
uint mod10 = prime % 10U;
|
||||
(mod10 == 3U || mod10 == 7U || prime == 11U || prime == 19U)
|
||||
.Should().BeTrue();
|
||||
lastThreePow2[i].Should().Be(prime * (ulong)prime);
|
||||
}
|
||||
|
||||
|
||||
lastOneWithoutThree.Should().NotBeEmpty();
|
||||
lastSevenWithoutThree.Should().NotBeEmpty();
|
||||
lastNineWithoutThree.Should().NotBeEmpty();
|
||||
|
||||
for (int i = 0; i < lastOneWithoutThree.Length; i++)
|
||||
{
|
||||
uint prime = lastOneWithoutThree[i];
|
||||
(prime % 10U != 3U).Should().BeTrue();
|
||||
if (i > 0)
|
||||
{
|
||||
prime.Should().BeGreaterThan(lastOneWithoutThree[i - 1]);
|
||||
}
|
||||
|
||||
lastOneWithoutThreePow2[i].Should().Be(prime * (ulong)prime);
|
||||
}
|
||||
|
||||
for (int i = 0; i < lastSevenWithoutThree.Length; i++)
|
||||
{
|
||||
uint prime = lastSevenWithoutThree[i];
|
||||
(prime % 10U != 3U).Should().BeTrue();
|
||||
if (i > 0)
|
||||
{
|
||||
prime.Should().BeGreaterThan(lastSevenWithoutThree[i - 1]);
|
||||
}
|
||||
|
||||
lastSevenWithoutThreePow2[i].Should().Be(prime * (ulong)prime);
|
||||
}
|
||||
|
||||
for (int i = 0; i < lastNineWithoutThree.Length; i++)
|
||||
{
|
||||
uint prime = lastNineWithoutThree[i];
|
||||
(prime % 10U != 3U).Should().BeTrue();
|
||||
if (i > 0)
|
||||
{
|
||||
prime.Should().BeGreaterThan(lastNineWithoutThree[i - 1]);
|
||||
}
|
||||
|
||||
lastNineWithoutThreePow2[i].Should().Be(prime * (ulong)prime);
|
||||
}
|
||||
|
||||
for (int i = 0; i < lastNine.Length; i++)
|
||||
{
|
||||
uint prime = lastNine[i];
|
||||
uint mod10 = prime % 10U;
|
||||
(mod10 == 3U || mod10 == 7U || mod10 == 9U || prime == 11U)
|
||||
.Should().BeTrue();
|
||||
lastNinePow2[i].Should().Be(prime * (ulong)prime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
internal sealed class MersenneCpuDivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||
{
|
||||
private bool _disposed;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_disposed = false;
|
||||
}
|
||||
|
||||
public void CheckDivisor(
|
||||
@@ -62,8 +65,14 @@ internal sealed class MersenneCpuDivisorScanSession : IMersenneNumberDivisorByDi
|
||||
}
|
||||
}
|
||||
|
||||
public void Return()
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(MersenneCpuDivisorScanSession), "Divisor scan session disposed twice.");
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
ThreadStaticPools.ReturnMersenneCpuDivisorSession(this);
|
||||
}
|
||||
}
|
||||
@@ -1,510 +1,284 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
|
||||
{
|
||||
private ulong _divisorLimit;
|
||||
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
|
||||
{
|
||||
get => _batchSize;
|
||||
set => _batchSize = Math.Max(1, value);
|
||||
}
|
||||
|
||||
public void ConfigureFromMaxPrime(ulong maxPrime)
|
||||
{
|
||||
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
||||
}
|
||||
|
||||
public ulong DivisorLimit
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return _divisorLimit;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||
|
||||
public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor)
|
||||
{
|
||||
ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||
|
||||
// The CPU by-divisor run always hands us primes with enormous divisor limits, so the fallback below never executes.
|
||||
// if (allowedMax < 3UL)
|
||||
// {
|
||||
// // EvenPerfectBitScanner routes primes below the small-divisor cutoff to the GPU path, so the CPU path still sees
|
||||
// // trivial candidates during targeted tests. Short-circuit here to keep those runs aligned with the production flow.
|
||||
// divisorsExhausted = true;
|
||||
// return true;
|
||||
// }
|
||||
|
||||
bool processedAll;
|
||||
|
||||
bool composite = CheckDivisors(
|
||||
prime,
|
||||
allowedMax,
|
||||
out processedAll,
|
||||
out divisor);
|
||||
|
||||
if (composite)
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
divisorsExhausted = processedAll || composite;
|
||||
divisor = 0UL;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||
{
|
||||
ulong divisorLimit = _divisorLimit;
|
||||
int length = primes.Length;
|
||||
for (int index = 0; index < length; index++)
|
||||
{
|
||||
allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit);
|
||||
}
|
||||
}
|
||||
|
||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
||||
{
|
||||
MersenneCpuDivisorScanSession? session = ThreadStaticPools.RentMersenneCpuDivisorSession();
|
||||
if (session is not null)
|
||||
{
|
||||
return session;
|
||||
}
|
||||
|
||||
return new MersenneCpuDivisorScanSession();
|
||||
}
|
||||
|
||||
private bool CheckDivisors(
|
||||
ulong prime,
|
||||
ulong allowedMax,
|
||||
out bool processedAll,
|
||||
out ulong foundDivisor)
|
||||
{
|
||||
foundDivisor = 0UL;
|
||||
// The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs.
|
||||
// Keeping the guard commented out documents the reasoning for benchmarks and tests.
|
||||
// if (allowedMax < 3UL)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
ref GpuUInt128WorkSet workSet = ref _divisorScanGpuWorkSet;
|
||||
|
||||
ref GpuUInt128 step = ref workSet.Step;
|
||||
step.High = 0UL;
|
||||
step.Low = prime;
|
||||
step.ShiftLeft(1);
|
||||
|
||||
ref GpuUInt128 limit = ref workSet.Limit;
|
||||
limit.High = 0UL;
|
||||
limit.Low = allowedMax;
|
||||
|
||||
ref GpuUInt128 divisor = ref workSet.Divisor;
|
||||
divisor.High = step.High;
|
||||
divisor.Low = step.Low;
|
||||
divisor.Add(1UL);
|
||||
if (divisor.CompareTo(limit) > 0)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Intentionally recomputes factorizations without a per-thread cache.
|
||||
// The previous factor cache recorded virtually no hits and only slowed down the scan.
|
||||
_ = DivisorCycleCache.Shared;
|
||||
|
||||
ulong stepHigh = step.High;
|
||||
ulong stepLow = step.Low;
|
||||
ulong limitHigh = limit.High;
|
||||
ulong divisorLow = divisor.Low;
|
||||
|
||||
// Keep the divisibility filters aligned with the divisor-cycle generator so the
|
||||
// CPU path never requests cycles that were skipped during cache creation.
|
||||
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
||||
ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit);
|
||||
|
||||
byte step10;
|
||||
byte step8;
|
||||
byte step3;
|
||||
byte step7;
|
||||
byte step11;
|
||||
|
||||
byte remainder10;
|
||||
byte remainder8;
|
||||
byte remainder3;
|
||||
byte remainder7;
|
||||
byte remainder11;
|
||||
|
||||
if (stepHigh == 0UL && limitHigh == 0UL)
|
||||
{
|
||||
step10 = (byte)(stepLow % 10UL);
|
||||
step8 = (byte)(stepLow & 7UL);
|
||||
step3 = (byte)(stepLow % 3UL);
|
||||
step7 = (byte)(stepLow % 7UL);
|
||||
step11 = (byte)(stepLow % 11UL);
|
||||
|
||||
remainder10 = (byte)(divisorLow % 10UL);
|
||||
remainder8 = (byte)(divisorLow & 7UL);
|
||||
remainder3 = (byte)(divisorLow % 3UL);
|
||||
remainder7 = (byte)(divisorLow % 7UL);
|
||||
remainder11 = (byte)(divisorLow % 11UL);
|
||||
|
||||
return CheckDivisors64(
|
||||
prime,
|
||||
stepLow,
|
||||
limit.Low,
|
||||
divisorLow,
|
||||
decimalMask,
|
||||
step10,
|
||||
step8,
|
||||
step3,
|
||||
step7,
|
||||
step11,
|
||||
remainder10,
|
||||
remainder8,
|
||||
remainder3,
|
||||
remainder7,
|
||||
remainder11,
|
||||
out processedAll,
|
||||
out foundDivisor);
|
||||
}
|
||||
|
||||
ulong divisorHigh = divisor.High;
|
||||
step10 = (byte)((((stepHigh % 10UL) * 6UL) + (stepLow % 10UL)) % 10UL);
|
||||
step8 = (byte)(stepLow % 8UL);
|
||||
step3 = (byte)(((stepHigh % 3UL) + (stepLow % 3UL)) % 3UL);
|
||||
step7 = (byte)((((stepHigh % 7UL) * 2UL) + (stepLow % 7UL)) % 7UL);
|
||||
step11 = (byte)((((stepHigh % 11UL) * 5UL) + (stepLow % 11UL)) % 11UL);
|
||||
|
||||
remainder10 = (byte)((((divisorHigh % 10UL) * 6UL) + (divisorLow % 10UL)) % 10UL);
|
||||
remainder8 = (byte)(divisorLow % 8UL);
|
||||
remainder3 = (byte)(((divisorHigh % 3UL) + (divisorLow % 3UL)) % 3UL);
|
||||
remainder7 = (byte)((((divisorHigh % 7UL) * 2UL) + (divisorLow % 7UL)) % 7UL);
|
||||
remainder11 = (byte)((((divisorHigh % 11UL) * 5UL) + (divisorLow % 11UL)) % 11UL);
|
||||
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
|
||||
while (divisor.CompareTo(limit) <= 0)
|
||||
{
|
||||
bool passesSmallModuli = remainder3 != 0 && remainder7 != 0 && remainder11 != 0;
|
||||
if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
|
||||
{
|
||||
ulong candidate = divisor.Low;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(candidate);
|
||||
ulong divisorCycle;
|
||||
// Divisors generated from 2 * k * p + 1 exceed the small-cycle snapshot when p >= 138,000,000, so the short path below never runs.
|
||||
// if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
// {
|
||||
// divisorCycle = cycleCache.GetCycleLength(candidate);
|
||||
// }
|
||||
// else
|
||||
{
|
||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
|
||||
gpu,
|
||||
candidate,
|
||||
prime,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
// Divisors produced by 2 * k * p + 1 always exceed PerfectNumberConstants.MaxQForDivisorCycles
|
||||
// for the exponents scanned here, so skip the unused cache fallback and compute directly.
|
||||
divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
|
||||
candidate,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorCycle = computedCycle;
|
||||
}
|
||||
}
|
||||
|
||||
if (divisorCycle == prime)
|
||||
{
|
||||
// A cycle equal to the tested exponent (which is prime in this path) guarantees that the candidate divides
|
||||
// the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p.
|
||||
foundDivisor = candidate;
|
||||
processedAll = true;
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||
}
|
||||
}
|
||||
|
||||
divisor.Add(step);
|
||||
remainder10 = AddMod10(remainder10, step10);
|
||||
remainder8 = AddMod8(remainder8, step8);
|
||||
remainder3 = AddMod3(remainder3, step3);
|
||||
remainder7 = AddMod7(remainder7, step7);
|
||||
remainder11 = AddMod11(remainder11, step11);
|
||||
}
|
||||
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool CheckDivisors64(
|
||||
ulong prime,
|
||||
ulong step,
|
||||
ulong limit,
|
||||
ulong divisor,
|
||||
ushort decimalMask,
|
||||
byte step10,
|
||||
byte step8,
|
||||
byte step3,
|
||||
byte step7,
|
||||
byte step11,
|
||||
byte remainder10,
|
||||
byte remainder8,
|
||||
byte remainder3,
|
||||
byte remainder7,
|
||||
byte remainder11,
|
||||
out bool processedAll,
|
||||
out ulong foundDivisor)
|
||||
{
|
||||
processedAll = false;
|
||||
foundDivisor = 0UL;
|
||||
|
||||
bool canAdvance = step <= limit;
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
if (!canAdvance)
|
||||
{
|
||||
if (divisor <= limit)
|
||||
{
|
||||
if (remainder3 != 0 && remainder7 != 0 && remainder11 != 0 && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
ulong divisorCycle;
|
||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
|
||||
gpu,
|
||||
divisor,
|
||||
prime,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
|
||||
divisor,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorCycle = computedCycle;
|
||||
}
|
||||
|
||||
if (divisorCycle == prime)
|
||||
{
|
||||
foundDivisor = divisor;
|
||||
processedAll = true;
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (divisor > limit)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong remainingIterations = ((limit - divisor) / step) + 1UL;
|
||||
|
||||
while (true)
|
||||
{
|
||||
bool passesSmallModuli = remainder3 != 0 && remainder7 != 0 && remainder11 != 0;
|
||||
if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
ulong divisorCycle;
|
||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
|
||||
gpu,
|
||||
divisor,
|
||||
prime,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
|
||||
divisor,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorCycle = computedCycle;
|
||||
}
|
||||
|
||||
if (divisorCycle == prime)
|
||||
{
|
||||
foundDivisor = divisor;
|
||||
processedAll = true;
|
||||
PrimeOrderCalculatorAccelerator.Return(gpu);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||
}
|
||||
}
|
||||
|
||||
remainingIterations--;
|
||||
if (remainingIterations == 0UL)
|
||||
{
|
||||
processedAll = true;
|
||||
foundDivisor = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
divisor += step;
|
||||
remainder10 = AddMod10(remainder10, step10);
|
||||
remainder8 = AddMod8(remainder8, step8);
|
||||
remainder3 = AddMod3(remainder3, step3);
|
||||
remainder7 = AddMod7(remainder7, step7);
|
||||
remainder11 = AddMod11(remainder11, step11);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
ulong residue = prime.Pow2MontgomeryModWithCycleCpu(divisorCycle, divisorData);
|
||||
return residue == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte AddMod3(byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 3;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte AddMod7(byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 7;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte AddMod10(byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 10;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte AddMod11(byte value, byte delta)
|
||||
{
|
||||
const int Modulus = 11;
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= Modulus)
|
||||
{
|
||||
sum -= Modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte AddMod8(byte value, byte delta)
|
||||
{
|
||||
return (byte)((value + delta) & 7);
|
||||
}
|
||||
|
||||
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime)
|
||||
{
|
||||
// The by-divisor CPU configuration only supplies primes greater than 1, so the guard below never trips.
|
||||
// if (maxPrime <= 1UL)
|
||||
// {
|
||||
// return 0UL;
|
||||
// }
|
||||
if (maxPrime - 1UL >= 64UL)
|
||||
{
|
||||
return ulong.MaxValue;
|
||||
}
|
||||
|
||||
return (1UL << (int)(maxPrime - 1UL)) - 1UL;
|
||||
}
|
||||
|
||||
private static ulong ComputeAllowedMaxDivisor(ulong prime, ulong divisorLimit)
|
||||
{
|
||||
// Production by-divisor scans only handle primes, so inputs never fall below 2.
|
||||
// if (prime <= 1UL)
|
||||
// {
|
||||
// return 0UL;
|
||||
// }
|
||||
if (prime - 1UL >= 64UL)
|
||||
{
|
||||
return divisorLimit;
|
||||
}
|
||||
|
||||
return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit);
|
||||
}
|
||||
private ulong _divisorLimit;
|
||||
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
|
||||
{
|
||||
get => _batchSize;
|
||||
set => _batchSize = Math.Max(1, value);
|
||||
}
|
||||
|
||||
public void ConfigureFromMaxPrime(ulong maxPrime)
|
||||
{
|
||||
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
||||
}
|
||||
|
||||
public ulong DivisorLimit
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return _divisorLimit;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||
|
||||
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
||||
{
|
||||
ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||
|
||||
// The CPU by-divisor run always hands us primes with enormous divisor limits, so the fallback below never executes.
|
||||
// if (allowedMax < 3UL)
|
||||
// {
|
||||
// // EvenPerfectBitScanner routes primes below the small-divisor cutoff to the GPU path, so the CPU path still sees
|
||||
// // trivial candidates during targeted tests. Short-circuit here to keep those runs aligned with the production flow.
|
||||
// divisorsExhausted = true;
|
||||
// return true;
|
||||
// }
|
||||
|
||||
bool processedAll;
|
||||
|
||||
bool composite = CheckDivisors(
|
||||
prime,
|
||||
allowedMax,
|
||||
out processedAll);
|
||||
|
||||
if (composite)
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
divisorsExhausted = processedAll || composite;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||
{
|
||||
ulong divisorLimit = _divisorLimit;
|
||||
int length = primes.Length;
|
||||
for (int index = 0; index < length; index++)
|
||||
{
|
||||
allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit);
|
||||
}
|
||||
}
|
||||
|
||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
||||
{
|
||||
MersenneCpuDivisorScanSession? session = ThreadStaticPools.RentMersenneCpuDivisorSession();
|
||||
if (session is not null)
|
||||
{
|
||||
return session;
|
||||
}
|
||||
|
||||
return new MersenneCpuDivisorScanSession();
|
||||
}
|
||||
|
||||
private bool CheckDivisors(
|
||||
ulong prime,
|
||||
ulong allowedMax,
|
||||
out bool processedAll)
|
||||
{
|
||||
processedAll = false;
|
||||
|
||||
// The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs.
|
||||
// Keeping the guard commented out documents the reasoning for benchmarks and tests.
|
||||
// if (allowedMax < 3UL)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
ref GpuUInt128WorkSet workSet = ref _divisorScanGpuWorkSet;
|
||||
|
||||
ref GpuUInt128 step = ref workSet.Step;
|
||||
step.High = 0UL;
|
||||
step.Low = prime;
|
||||
step.ShiftLeft(1);
|
||||
|
||||
ref GpuUInt128 limit = ref workSet.Limit;
|
||||
limit.High = 0UL;
|
||||
limit.Low = allowedMax;
|
||||
|
||||
ref GpuUInt128 divisor = ref workSet.Divisor;
|
||||
divisor.High = step.High;
|
||||
divisor.Low = step.Low;
|
||||
divisor.Add(1UL);
|
||||
if (divisor.CompareTo(limit) > 0)
|
||||
{
|
||||
processedAll = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Intentionally recomputes factorizations without a per-thread cache.
|
||||
// The previous factor cache recorded virtually no hits and only slowed down the scan.
|
||||
DivisorCycleCache cycleCache = DivisorCycleCache.Shared;
|
||||
|
||||
ulong stepHigh = step.High;
|
||||
ulong stepLow = step.Low;
|
||||
byte step10 = (byte)((((stepHigh % 10UL) * 6UL) + (stepLow % 10UL)) % 10UL);
|
||||
byte step8 = (byte)(stepLow % 8UL);
|
||||
byte step5 = (byte)(((stepHigh % 5UL) + (stepLow % 5UL)) % 5UL);
|
||||
byte step3 = (byte)(((stepHigh % 3UL) + (stepLow % 3UL)) % 3UL);
|
||||
byte step7 = (byte)((((stepHigh % 7UL) * 2UL) + (stepLow % 7UL)) % 7UL);
|
||||
byte step11 = (byte)((((stepHigh % 11UL) * 5UL) + (stepLow % 11UL)) % 11UL);
|
||||
|
||||
ulong divisorHigh = divisor.High;
|
||||
ulong divisorLow = divisor.Low;
|
||||
byte remainder10 = (byte)((((divisorHigh % 10UL) * 6UL) + (divisorLow % 10UL)) % 10UL);
|
||||
byte remainder8 = (byte)(divisorLow % 8UL);
|
||||
byte remainder5 = (byte)(((divisorHigh % 5UL) + (divisorLow % 5UL)) % 5UL);
|
||||
byte remainder3 = (byte)(((divisorHigh % 3UL) + (divisorLow % 3UL)) % 3UL);
|
||||
byte remainder7 = (byte)((((divisorHigh % 7UL) * 2UL) + (divisorLow % 7UL)) % 7UL);
|
||||
byte remainder11 = (byte)((((divisorHigh % 11UL) * 5UL) + (divisorLow % 11UL)) % 11UL);
|
||||
|
||||
// Keep the divisibility filters aligned with the divisor-cycle generator so the
|
||||
// CPU path never requests cycles that were skipped during cache creation.
|
||||
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
|
||||
ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit);
|
||||
|
||||
while (divisor.CompareTo(limit) <= 0)
|
||||
{
|
||||
ulong candidate = divisor.Low;
|
||||
|
||||
bool admissible = ((decimalMask >> remainder10) & 1) != 0
|
||||
&& (remainder8 == 1 || remainder8 == 7)
|
||||
&& remainder3 != 0
|
||||
&& remainder5 != 0
|
||||
&& remainder7 != 0
|
||||
&& remainder11 != 0;
|
||||
|
||||
if (admissible)
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(candidate);
|
||||
ulong divisorCycle;
|
||||
// Divisors generated from 2 * k * p + 1 exceed the small-cycle snapshot when p >= 138,000,000, so the short path below never runs.
|
||||
// if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
// {
|
||||
// divisorCycle = cycleCache.GetCycleLength(candidate);
|
||||
// }
|
||||
// else
|
||||
{
|
||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
|
||||
candidate,
|
||||
prime,
|
||||
divisorData,
|
||||
out ulong computedCycle,
|
||||
out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
// Divisors produced by 2 * k * p + 1 always exceed PerfectNumberConstants.MaxQForDivisorCycles
|
||||
// for the exponents scanned here, so skip the unused cache fallback and compute directly.
|
||||
divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
|
||||
candidate,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorCycle = computedCycle;
|
||||
}
|
||||
}
|
||||
|
||||
if (divisorCycle == prime)
|
||||
{
|
||||
// A cycle equal to the tested exponent (which is prime in this path) guarantees that the candidate divides
|
||||
// the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p.
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||
}
|
||||
}
|
||||
|
||||
divisor.Add(step);
|
||||
remainder10 = AddMod(remainder10, step10, (byte)10);
|
||||
remainder8 = AddMod8(remainder8, step8);
|
||||
remainder5 = AddMod(remainder5, step5, (byte)5);
|
||||
remainder3 = AddMod(remainder3, step3, (byte)3);
|
||||
remainder7 = AddMod(remainder7, step7, (byte)7);
|
||||
remainder11 = AddMod(remainder11, step11, (byte)11);
|
||||
}
|
||||
|
||||
processedAll = divisor.CompareTo(limit) > 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
ulong residue = prime.Pow2MontgomeryModWithCycleCpu(divisorCycle, divisorData);
|
||||
return residue == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte AddMod(byte value, byte delta, byte modulus)
|
||||
{
|
||||
int sum = value + delta;
|
||||
|
||||
if (sum >= modulus)
|
||||
{
|
||||
sum -= modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte AddMod8(byte value, byte delta)
|
||||
{
|
||||
return (byte)((value + delta) & 7);
|
||||
}
|
||||
|
||||
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime)
|
||||
{
|
||||
// The by-divisor CPU configuration only supplies primes greater than 1, so the guard below never trips.
|
||||
// if (maxPrime <= 1UL)
|
||||
// {
|
||||
// return 0UL;
|
||||
// }
|
||||
if (maxPrime - 1UL >= 64UL)
|
||||
{
|
||||
return ulong.MaxValue;
|
||||
}
|
||||
|
||||
return (1UL << (int)(maxPrime - 1UL)) - 1UL;
|
||||
}
|
||||
|
||||
private static ulong ComputeAllowedMaxDivisor(ulong prime, ulong divisorLimit)
|
||||
{
|
||||
// Production by-divisor scans only handle primes, so inputs never fall below 2.
|
||||
// if (prime <= 1UL)
|
||||
// {
|
||||
// return 0UL;
|
||||
// }
|
||||
if (prime - 1UL >= 64UL)
|
||||
{
|
||||
return divisorLimit;
|
||||
}
|
||||
|
||||
return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
namespace PerfectNumbers.Core;
|
||||
|
||||
internal static class DeterministicRandomCpu
|
||||
internal static class DeterministicRandom
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static ulong s_threadState;
|
||||
@@ -1,377 +1,331 @@
|
||||
using System.Buffers;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace PerfectNumbers.Core;
|
||||
|
||||
public sealed class DivisorCycleCache
|
||||
{
|
||||
private const byte ByteZero = 0;
|
||||
private const byte ByteOne = 1;
|
||||
private const int StackBufferThreshold = 128;
|
||||
private readonly Accelerator _accelerator;
|
||||
private readonly int _acceleratorIndex;
|
||||
private readonly Action<AcceleratorStream, Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>> _gpuKernel;
|
||||
private volatile ulong[] _snapshot;
|
||||
private volatile bool _useGpuGeneration = true;
|
||||
private readonly int _divisorCyclesBatchSize;
|
||||
private static int _sharedDivisorCyclesBatchSize = GpuConstants.GpuCycleStepsPerInvocation;
|
||||
private const byte ByteZero = 0;
|
||||
private const byte ByteOne = 1;
|
||||
private const int StackBufferThreshold = 128;
|
||||
|
||||
private const int CycleCacheTrackingLimit = 500_000;
|
||||
private static readonly HashSet<ulong> CycleCacheTrackedDivisors = new();
|
||||
private static readonly object CycleCacheTrackingLock = new();
|
||||
private static ulong CycleCacheHitCount;
|
||||
private static bool CycleCacheTrackingDisabled;
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>> _gpuKernelCache = new(AcceleratorReferenceComparer.Instance);
|
||||
private volatile ulong[] _snapshot;
|
||||
private volatile bool _useGpuGeneration = true;
|
||||
private readonly int _divisorCyclesBatchSize;
|
||||
private static int _sharedDivisorCyclesBatchSize = GpuConstants.GpuCycleStepsPerInvocation;
|
||||
|
||||
public static void SetDivisorCyclesBatchSize(int divisorCyclesBatchSize)
|
||||
{
|
||||
_sharedDivisorCyclesBatchSize = Math.Max(1, divisorCyclesBatchSize);
|
||||
}
|
||||
private const int CycleCacheTrackingLimit = 500_000;
|
||||
private static readonly HashSet<ulong> CycleCacheTrackedDivisors = new();
|
||||
private static readonly object CycleCacheTrackingLock = new();
|
||||
private static ulong CycleCacheHitCount;
|
||||
private static bool CycleCacheTrackingDisabled;
|
||||
|
||||
public static DivisorCycleCache Shared { get; } = new DivisorCycleCache(_sharedDivisorCyclesBatchSize);
|
||||
public static void SetDivisorCyclesBatchSize(int divisorCyclesBatchSize)
|
||||
{
|
||||
_sharedDivisorCyclesBatchSize = Math.Max(1, divisorCyclesBatchSize);
|
||||
}
|
||||
|
||||
public int PreferredBatchSize => _divisorCyclesBatchSize;
|
||||
public static DivisorCycleCache Shared { get; } = new DivisorCycleCache(_sharedDivisorCyclesBatchSize);
|
||||
|
||||
// private static readonly Accelerator[] _accelerators;
|
||||
public int PreferredBatchSize => _divisorCyclesBatchSize;
|
||||
|
||||
private DivisorCycleCache(int divisorCyclesBatchSize)
|
||||
{
|
||||
_divisorCyclesBatchSize = Math.Max(1, divisorCyclesBatchSize);
|
||||
var acceleratorIndex = AcceleratorPool.Shared.Rent();
|
||||
_acceleratorIndex = acceleratorIndex;
|
||||
Accelerator accelerator = AcceleratorPool.Shared.Accelerators[acceleratorIndex];
|
||||
_accelerator = accelerator;
|
||||
_gpuKernel = LoadKernel(accelerator);
|
||||
_snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||
}
|
||||
private DivisorCycleCache(int divisorCyclesBatchSize)
|
||||
{
|
||||
_divisorCyclesBatchSize = Math.Max(1, divisorCyclesBatchSize);
|
||||
_snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||
}
|
||||
|
||||
public void ConfigureGeneratorDevice(bool useGpu)
|
||||
{
|
||||
_useGpuGeneration = useGpu;
|
||||
}
|
||||
public void ConfigureGeneratorDevice(bool useGpu)
|
||||
{
|
||||
_useGpuGeneration = useGpu;
|
||||
}
|
||||
|
||||
public void RefreshSnapshot()
|
||||
{
|
||||
_snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||
}
|
||||
public void RefreshSnapshot()
|
||||
{
|
||||
_snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||
}
|
||||
|
||||
public ulong GetCycleLength(ulong divisor, bool skipPrimeOrderHeuristic = false)
|
||||
{
|
||||
if (!skipPrimeOrderHeuristic)
|
||||
{
|
||||
Span<ulong> result = stackalloc ulong[1];
|
||||
ReadOnlySpan<ulong> singleDivisor = stackalloc ulong[1] { divisor };
|
||||
GetCycleLengths(singleDivisor, result);
|
||||
return result[0];
|
||||
}
|
||||
public ulong GetCycleLength(ulong divisor, bool skipPrimeOrderHeuristic = false)
|
||||
{
|
||||
if (!skipPrimeOrderHeuristic)
|
||||
{
|
||||
Span<ulong> result = stackalloc ulong[1];
|
||||
ReadOnlySpan<ulong> singleDivisor = stackalloc ulong[1] { divisor };
|
||||
GetCycleLengths(singleDivisor, result);
|
||||
return result[0];
|
||||
}
|
||||
|
||||
// The skipPrimeOrderHeuristic path is only used for divisors produced by the CPU by-divisor scanner,
|
||||
// which always generates values greater than one. Keep this guard disabled here to avoid repeating the
|
||||
// validation already performed by the general GetCycleLengths path.
|
||||
// if (divisor <= 1UL)
|
||||
// {
|
||||
// throw new InvalidDataException("Divisor must be > 1");
|
||||
// }
|
||||
// The skipPrimeOrderHeuristic path is only used for divisors produced by the CPU by-divisor scanner,
|
||||
// which always generates values greater than one. Keep this guard disabled here to avoid repeating the
|
||||
// validation already performed by the general GetCycleLengths path.
|
||||
// if (divisor <= 1UL)
|
||||
// {
|
||||
// throw new InvalidDataException("Divisor must be > 1");
|
||||
// }
|
||||
|
||||
ulong[] snapshot = _snapshot;
|
||||
if (divisor < (ulong)snapshot.Length)
|
||||
{
|
||||
ulong cached = snapshot[divisor];
|
||||
if (cached == 0UL)
|
||||
{
|
||||
throw new InvalidDataException($"Divisor cycle is missing for {divisor}");
|
||||
}
|
||||
ulong[] snapshot = _snapshot;
|
||||
if (divisor < (ulong)snapshot.Length)
|
||||
{
|
||||
ulong cached = snapshot[divisor];
|
||||
if (cached == 0UL)
|
||||
{
|
||||
throw new InvalidDataException($"Divisor cycle is missing for {divisor}");
|
||||
}
|
||||
|
||||
return cached;
|
||||
}
|
||||
// ObserveCycleCacheDivisor(divisor);
|
||||
return cached;
|
||||
}
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
return MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: true);
|
||||
}
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
return MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: true);
|
||||
}
|
||||
|
||||
public void GetCycleLengths(ReadOnlySpan<ulong> divisors, Span<ulong> cycles)
|
||||
{
|
||||
ulong[] snapshot = _snapshot;
|
||||
int length = divisors.Length;
|
||||
int[]? rentedMissing = null;
|
||||
Span<int> missingBuffer = length <= StackBufferThreshold
|
||||
? stackalloc int[length]
|
||||
: new Span<int>(rentedMissing = ThreadStaticPools.IntPool.Rent(length), 0, length);
|
||||
public void GetCycleLengths(ReadOnlySpan<ulong> divisors, Span<ulong> cycles)
|
||||
{
|
||||
ulong[] snapshot = _snapshot;
|
||||
int length = divisors.Length;
|
||||
int[]? rentedMissing = null;
|
||||
Span<int> missingBuffer = length <= StackBufferThreshold
|
||||
? stackalloc int[length]
|
||||
: new Span<int>(rentedMissing = ArrayPool<int>.Shared.Rent(length), 0, length);
|
||||
|
||||
int missingCount = 0;
|
||||
int missingCount = 0;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
ulong divisor = divisors[i];
|
||||
if (divisor <= 1UL)
|
||||
{
|
||||
throw new InvalidDataException("Divisor must be > 1");
|
||||
}
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
ulong divisor = divisors[i];
|
||||
if (divisor <= 1UL)
|
||||
{
|
||||
throw new InvalidDataException("Divisor must be > 1");
|
||||
}
|
||||
|
||||
if (divisor < (ulong)snapshot.Length)
|
||||
{
|
||||
ulong cached = snapshot[divisor];
|
||||
if (cached == 0UL)
|
||||
{
|
||||
throw new InvalidDataException($"Divisor cycle is missing for {divisor}");
|
||||
}
|
||||
if (divisor < (ulong)snapshot.Length)
|
||||
{
|
||||
ulong cached = snapshot[divisor];
|
||||
if (cached == 0UL)
|
||||
{
|
||||
throw new InvalidDataException($"Divisor cycle is missing for {divisor}");
|
||||
}
|
||||
|
||||
cycles[i] = cached;
|
||||
}
|
||||
else
|
||||
{
|
||||
missingBuffer[missingCount++] = i;
|
||||
}
|
||||
}
|
||||
// ObserveCycleCacheDivisor(divisor);
|
||||
cycles[i] = cached;
|
||||
}
|
||||
else
|
||||
{
|
||||
missingBuffer[missingCount++] = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (missingCount != 0)
|
||||
{
|
||||
ReadOnlySpan<int> missingIndices = missingBuffer[..missingCount];
|
||||
if (_useGpuGeneration)
|
||||
{
|
||||
ComputeCyclesGpu(divisors, cycles, missingIndices);
|
||||
}
|
||||
else
|
||||
{
|
||||
ComputeCyclesCpu(divisors, cycles, missingIndices);
|
||||
}
|
||||
}
|
||||
if (missingCount != 0)
|
||||
{
|
||||
ReadOnlySpan<int> missingIndices = missingBuffer[..missingCount];
|
||||
if (_useGpuGeneration)
|
||||
{
|
||||
ComputeCyclesGpu(divisors, cycles, missingIndices);
|
||||
}
|
||||
else
|
||||
{
|
||||
ComputeCyclesCpu(divisors, cycles, missingIndices);
|
||||
}
|
||||
}
|
||||
|
||||
if (rentedMissing is not null)
|
||||
{
|
||||
ThreadStaticPools.IntPool.Return(rentedMissing, clearArray: false);
|
||||
}
|
||||
}
|
||||
if (rentedMissing is not null)
|
||||
{
|
||||
ArrayPool<int>.Shared.Return(rentedMissing, clearArray: false);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ObserveCycleCacheDivisor(ulong divisor)
|
||||
{
|
||||
bool logHit = false;
|
||||
ulong hitNumber = 0UL;
|
||||
bool trackingDisabledNow = false;
|
||||
private static void ObserveCycleCacheDivisor(ulong divisor)
|
||||
{
|
||||
bool logHit = false;
|
||||
ulong hitNumber = 0UL;
|
||||
bool trackingDisabledNow = false;
|
||||
|
||||
lock (CycleCacheTrackingLock)
|
||||
{
|
||||
if (CycleCacheTrackingDisabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lock (CycleCacheTrackingLock)
|
||||
{
|
||||
if (CycleCacheTrackingDisabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CycleCacheTrackedDivisors.Add(divisor))
|
||||
{
|
||||
CycleCacheHitCount++;
|
||||
hitNumber = CycleCacheHitCount;
|
||||
logHit = true;
|
||||
}
|
||||
else if (CycleCacheTrackedDivisors.Count >= CycleCacheTrackingLimit)
|
||||
{
|
||||
CycleCacheTrackingDisabled = true;
|
||||
CycleCacheTrackedDivisors.Clear();
|
||||
trackingDisabledNow = true;
|
||||
}
|
||||
}
|
||||
if (!CycleCacheTrackedDivisors.Add(divisor))
|
||||
{
|
||||
CycleCacheHitCount++;
|
||||
hitNumber = CycleCacheHitCount;
|
||||
logHit = true;
|
||||
}
|
||||
else if (CycleCacheTrackedDivisors.Count >= CycleCacheTrackingLimit)
|
||||
{
|
||||
CycleCacheTrackingDisabled = true;
|
||||
CycleCacheTrackedDivisors.Clear();
|
||||
trackingDisabledNow = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (logHit)
|
||||
{
|
||||
Console.WriteLine($"Cycle cache hit for divisor {divisor} ({hitNumber})");
|
||||
}
|
||||
else if (trackingDisabledNow)
|
||||
{
|
||||
Console.WriteLine("Cycle cache hit tracking disabled after exceeding the tracking limit.");
|
||||
}
|
||||
}
|
||||
if (logHit)
|
||||
{
|
||||
Console.WriteLine($"Cycle cache hit for divisor {divisor} ({hitNumber})");
|
||||
}
|
||||
else if (trackingDisabledNow)
|
||||
{
|
||||
Console.WriteLine("Cycle cache hit tracking disabled after exceeding the tracking limit.");
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ComputeCyclesCpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices)
|
||||
{
|
||||
for (int i = 0; i < indices.Length; i++)
|
||||
{
|
||||
int targetIndex = indices[i];
|
||||
ulong divisor = divisors[targetIndex];
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData);
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ComputeCyclesCpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices)
|
||||
{
|
||||
for (int i = 0; i < indices.Length; i++)
|
||||
{
|
||||
int targetIndex = indices[i];
|
||||
ulong divisor = divisors[targetIndex];
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData);
|
||||
}
|
||||
}
|
||||
|
||||
private void ComputeCyclesGpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices)
|
||||
{
|
||||
int count = indices.Length;
|
||||
ulong[]? rentedDivisors = null;
|
||||
Span<ulong> workDivisors = count <= StackBufferThreshold
|
||||
? stackalloc ulong[count]
|
||||
: new Span<ulong>(rentedDivisors = ThreadStaticPools.UlongPool.Rent(count), 0, count);
|
||||
private void ComputeCyclesGpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices)
|
||||
{
|
||||
int count = indices.Length;
|
||||
ulong[]? rentedDivisors = null;
|
||||
Span<ulong> workDivisors = count <= StackBufferThreshold
|
||||
? stackalloc ulong[count]
|
||||
: new Span<ulong>(rentedDivisors = ArrayPool<ulong>.Shared.Rent(count), 0, count);
|
||||
|
||||
ulong[]? rentedResults = null;
|
||||
Span<ulong> workResults = count <= StackBufferThreshold
|
||||
? stackalloc ulong[count]
|
||||
: new Span<ulong>(rentedResults = ThreadStaticPools.UlongPool.Rent(count), 0, count);
|
||||
ulong[]? rentedResults = null;
|
||||
Span<ulong> workResults = count <= StackBufferThreshold
|
||||
? stackalloc ulong[count]
|
||||
: new Span<ulong>(rentedResults = ArrayPool<ulong>.Shared.Rent(count), 0, count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
workDivisors[i] = divisors[indices[i]];
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
workDivisors[i] = divisors[indices[i]];
|
||||
}
|
||||
|
||||
ComputeCyclesGpuCore(workDivisors, workResults);
|
||||
ComputeCyclesGpuCore(workDivisors, workResults);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
cycles[indices[i]] = workResults[i];
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
cycles[indices[i]] = workResults[i];
|
||||
}
|
||||
|
||||
if (rentedDivisors is not null)
|
||||
{
|
||||
ThreadStaticPools.UlongPool.Return(rentedDivisors, clearArray: false);
|
||||
ThreadStaticPools.UlongPool.Return(rentedResults!, clearArray: false);
|
||||
}
|
||||
}
|
||||
if (rentedDivisors is not null)
|
||||
{
|
||||
ArrayPool<ulong>.Shared.Return(rentedDivisors, clearArray: false);
|
||||
ArrayPool<ulong>.Shared.Return(rentedResults!, clearArray: false);
|
||||
}
|
||||
}
|
||||
|
||||
private const int DefaultCapacity = 128;
|
||||
private void ComputeCyclesGpuCore(ReadOnlySpan<ulong> divisors, Span<ulong> destination)
|
||||
{
|
||||
int length = divisors.Length;
|
||||
var gpuLease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||
var execution = gpuLease.EnterExecutionScope();
|
||||
|
||||
[ThreadStatic]
|
||||
private static MemoryBuffer1D<ulong, Stride1D.Dense>? divisorBuffer;
|
||||
Accelerator accelerator = gpuLease.Accelerator;
|
||||
var kernel = _gpuKernelCache.GetOrAdd(accelerator, LoadKernel);
|
||||
|
||||
[ThreadStatic]
|
||||
private static MemoryBuffer1D<ulong, Stride1D.Dense>? powBuffer;
|
||||
using MemoryBuffer1D<ulong, Stride1D.Dense> divisorBuffer = accelerator.Allocate1D<ulong>(length);
|
||||
using MemoryBuffer1D<ulong, Stride1D.Dense> powBuffer = accelerator.Allocate1D<ulong>(length);
|
||||
using MemoryBuffer1D<ulong, Stride1D.Dense> orderBuffer = accelerator.Allocate1D<ulong>(length);
|
||||
using MemoryBuffer1D<ulong, Stride1D.Dense> resultBuffer = accelerator.Allocate1D<ulong>(length);
|
||||
using MemoryBuffer1D<byte, Stride1D.Dense> statusBuffer = accelerator.Allocate1D<byte>(length);
|
||||
|
||||
[ThreadStatic]
|
||||
private static MemoryBuffer1D<ulong, Stride1D.Dense>? orderBuffer;
|
||||
ulong[]? rentedPow = null;
|
||||
Span<ulong> powSpan = length <= StackBufferThreshold
|
||||
? stackalloc ulong[length]
|
||||
: new Span<ulong>(rentedPow = ArrayPool<ulong>.Shared.Rent(length), 0, length);
|
||||
|
||||
[ThreadStatic]
|
||||
private static MemoryBuffer1D<ulong, Stride1D.Dense>? resultBuffer;
|
||||
ulong[]? rentedOrder = null;
|
||||
Span<ulong> orderSpan = length <= StackBufferThreshold
|
||||
? stackalloc ulong[length]
|
||||
: new Span<ulong>(rentedOrder = ArrayPool<ulong>.Shared.Rent(length), 0, length);
|
||||
|
||||
[ThreadStatic]
|
||||
private static MemoryBuffer1D<byte, Stride1D.Dense>? statusBuffer;
|
||||
ulong[]? rentedResult = null;
|
||||
Span<ulong> resultSpan = length <= StackBufferThreshold
|
||||
? stackalloc ulong[length]
|
||||
: new Span<ulong>(rentedResult = ArrayPool<ulong>.Shared.Rent(length), 0, length);
|
||||
|
||||
private void EnsureCapacity(int requiredCapacity)
|
||||
{
|
||||
if (requiredCapacity <= (divisorBuffer?.Length ?? 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
byte[]? rentedStatus = null;
|
||||
Span<byte> statusSpan = length <= StackBufferThreshold
|
||||
? stackalloc byte[length]
|
||||
: new Span<byte>(rentedStatus = ArrayPool<byte>.Shared.Rent(length), 0, length);
|
||||
|
||||
if (divisorBuffer != null)
|
||||
{
|
||||
divisorBuffer.Dispose();
|
||||
powBuffer!.Dispose();
|
||||
orderBuffer!.Dispose();
|
||||
resultBuffer!.Dispose();
|
||||
statusBuffer!.Dispose();
|
||||
}
|
||||
ref ulong divisorRef = ref MemoryMarshal.GetReference(divisors);
|
||||
divisorBuffer.View.CopyFromCPU(ref divisorRef, length);
|
||||
|
||||
Accelerator accelerator = _accelerator;
|
||||
lock(accelerator)
|
||||
{
|
||||
divisorBuffer = accelerator.Allocate1D<ulong>(requiredCapacity);
|
||||
powBuffer = accelerator.Allocate1D<ulong>(requiredCapacity);
|
||||
orderBuffer = accelerator.Allocate1D<ulong>(requiredCapacity);
|
||||
resultBuffer = accelerator.Allocate1D<ulong>(requiredCapacity);
|
||||
statusBuffer = accelerator.Allocate1D<byte>(requiredCapacity);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
ulong divisor = divisors[i];
|
||||
ulong initialPow = 2UL;
|
||||
if (initialPow >= divisor)
|
||||
{
|
||||
initialPow -= divisor;
|
||||
}
|
||||
|
||||
private void ComputeCyclesGpuCore(ReadOnlySpan<ulong> divisors, Span<ulong> destination)
|
||||
{
|
||||
int length = divisors.Length;
|
||||
powSpan[i] = initialPow;
|
||||
orderSpan[i] = 1UL;
|
||||
resultSpan[i] = 0UL;
|
||||
statusSpan[i] = ByteZero;
|
||||
}
|
||||
|
||||
EnsureCapacity(length);
|
||||
powBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(powSpan), length);
|
||||
orderBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(orderSpan), length);
|
||||
resultBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(resultSpan), length);
|
||||
statusBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(statusSpan), length);
|
||||
|
||||
ulong[]? rentedPow = null;
|
||||
Span<ulong> powSpan = length <= StackBufferThreshold
|
||||
? stackalloc ulong[length]
|
||||
: new Span<ulong>(rentedPow = ThreadStaticPools.UlongPool.Rent(length), 0, length);
|
||||
int pending;
|
||||
do
|
||||
{
|
||||
kernel(length, _divisorCyclesBatchSize, divisorBuffer.View, powBuffer.View, orderBuffer.View, resultBuffer.View, statusBuffer.View);
|
||||
accelerator.Synchronize();
|
||||
|
||||
ulong[]? rentedOrder = null;
|
||||
Span<ulong> orderSpan = length <= StackBufferThreshold
|
||||
? stackalloc ulong[length]
|
||||
: new Span<ulong>(rentedOrder = ThreadStaticPools.UlongPool.Rent(length), 0, length);
|
||||
statusBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(statusSpan), length);
|
||||
pending = 0;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (statusSpan[i] == ByteZero)
|
||||
{
|
||||
pending++;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (pending > 0);
|
||||
|
||||
ulong[]? rentedResult = null;
|
||||
Span<ulong> resultSpan = length <= StackBufferThreshold
|
||||
? stackalloc ulong[length]
|
||||
: new Span<ulong>(rentedResult = ThreadStaticPools.UlongPool.Rent(length), 0, length);
|
||||
resultBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(resultSpan), length);
|
||||
resultSpan.CopyTo(destination);
|
||||
if (rentedPow is not null)
|
||||
{
|
||||
ArrayPool<ulong>.Shared.Return(rentedPow, clearArray: false);
|
||||
ArrayPool<ulong>.Shared.Return(rentedOrder!, clearArray: false);
|
||||
ArrayPool<ulong>.Shared.Return(rentedResult!, clearArray: false);
|
||||
ArrayPool<byte>.Shared.Return(rentedStatus!, clearArray: false);
|
||||
}
|
||||
|
||||
byte[]? rentedStatus = null;
|
||||
Span<byte> statusSpan = length <= StackBufferThreshold
|
||||
? stackalloc byte[length]
|
||||
: new Span<byte>(rentedStatus = ThreadStaticPools.BytePool.Rent(length), 0, length);
|
||||
execution.Dispose();
|
||||
gpuLease.Dispose();
|
||||
}
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
ulong divisor = divisors[i];
|
||||
ulong initialPow = 2UL;
|
||||
if (initialPow >= divisor)
|
||||
{
|
||||
initialPow -= divisor;
|
||||
}
|
||||
private static Action<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>> LoadKernel(Accelerator accelerator)
|
||||
{
|
||||
return accelerator.LoadAutoGroupedStreamKernel<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>(DivisorCycleKernels.GpuAdvanceDivisorCyclesKernel);
|
||||
}
|
||||
|
||||
powSpan[i] = initialPow;
|
||||
orderSpan[i] = 1UL;
|
||||
resultSpan[i] = 0UL;
|
||||
statusSpan[i] = ByteZero;
|
||||
}
|
||||
private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
|
||||
{
|
||||
internal static AcceleratorReferenceComparer Instance { get; } = new();
|
||||
|
||||
// GpuPrimeWorkLimiter.Acquire();
|
||||
int pending;
|
||||
var kernel = _gpuKernel;
|
||||
var acceleratorIndex = _acceleratorIndex;
|
||||
var stream = AcceleratorStreamPool.Rent(acceleratorIndex);
|
||||
public bool Equals(Accelerator? x, Accelerator? y) => ReferenceEquals(x, y);
|
||||
|
||||
divisorBuffer!.View.CopyFromCPU(stream, divisors);
|
||||
powBuffer!.View.CopyFromCPU(stream, powSpan);
|
||||
orderBuffer!.View.CopyFromCPU(stream, orderSpan);
|
||||
resultBuffer!.View.CopyFromCPU(stream, resultSpan);
|
||||
statusBuffer!.View.CopyFromCPU(stream, statusSpan);
|
||||
|
||||
do
|
||||
{
|
||||
kernel(stream, length, _divisorCyclesBatchSize, divisorBuffer.View, powBuffer.View, orderBuffer.View, resultBuffer.View, statusBuffer.View);
|
||||
|
||||
statusBuffer.View.CopyToCPU(stream, statusSpan);
|
||||
stream.Synchronize();
|
||||
|
||||
pending = 0;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (statusSpan[i] == ByteZero)
|
||||
{
|
||||
pending++;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (pending > 0);
|
||||
|
||||
resultBuffer.View.CopyToCPU(stream, resultSpan);
|
||||
stream.Synchronize();
|
||||
AcceleratorStreamPool.Return(acceleratorIndex, stream);
|
||||
|
||||
resultSpan.CopyTo(destination);
|
||||
if (rentedPow is not null)
|
||||
{
|
||||
ThreadStaticPools.UlongPool.Return(rentedPow, clearArray: false);
|
||||
ThreadStaticPools.UlongPool.Return(rentedOrder!, clearArray: false);
|
||||
ThreadStaticPools.UlongPool.Return(rentedResult!, clearArray: false);
|
||||
ThreadStaticPools.BytePool.Return(rentedStatus!, clearArray: false);
|
||||
}
|
||||
|
||||
// GpuPrimeWorkLimiter.Release();
|
||||
}
|
||||
|
||||
private static Action<AcceleratorStream, Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>> LoadKernel(Accelerator accelerator)
|
||||
{
|
||||
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>(DivisorCycleKernels.GpuAdvanceDivisorCyclesKernel);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>>();
|
||||
}
|
||||
|
||||
private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
|
||||
{
|
||||
internal static AcceleratorReferenceComparer Instance { get; } = new();
|
||||
|
||||
public bool Equals(Accelerator? x, Accelerator? y) => ReferenceEquals(x, y);
|
||||
|
||||
public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj);
|
||||
}
|
||||
public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,272 +4,40 @@ namespace PerfectNumbers.Core;
|
||||
|
||||
internal static class DivisorGenerator
|
||||
{
|
||||
public static readonly uint[] SmallPrimes;
|
||||
public static readonly ulong[] SmallPrimesPow2;
|
||||
public static readonly uint[] SmallPrimesLastOne;
|
||||
public static readonly ulong[] SmallPrimesPow2LastOne;
|
||||
public static readonly uint[] SmallPrimesLastSeven;
|
||||
public static readonly ulong[] SmallPrimesPow2LastSeven;
|
||||
public static readonly uint[] SmallPrimesLastThree;
|
||||
public static readonly ulong[] SmallPrimesPow2LastThree;
|
||||
public static readonly uint[] SmallPrimesLastNine;
|
||||
public static readonly ulong[] SmallPrimesPow2LastNine;
|
||||
public static readonly uint[] SmallPrimesLastOneWithoutLastThree;
|
||||
public static readonly ulong[] SmallPrimesPow2LastOneWithoutLastThree;
|
||||
public static readonly uint[] SmallPrimesLastSevenWithoutLastThree;
|
||||
public static readonly ulong[] SmallPrimesPow2LastSevenWithoutLastThree;
|
||||
public static readonly uint[] SmallPrimesLastNineWithoutLastThree;
|
||||
public static readonly ulong[] SmallPrimesPow2LastNineWithoutLastThree;
|
||||
|
||||
private const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9);
|
||||
private const ushort DecimalMaskWhenLastIsThree = (1 << 1) | (1 << 3) | (1 << 7) | (1 << 9);
|
||||
private const ushort DecimalMaskWhenLastIsNine = (1 << 1) | (1 << 3) | (1 << 7) | (1 << 9);
|
||||
private const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 9);
|
||||
|
||||
static DivisorGenerator()
|
||||
{
|
||||
BuildSmallPrimes(
|
||||
out SmallPrimes,
|
||||
out SmallPrimesPow2,
|
||||
out SmallPrimesLastOne,
|
||||
out SmallPrimesPow2LastOne,
|
||||
out SmallPrimesLastSeven,
|
||||
out SmallPrimesPow2LastSeven,
|
||||
out SmallPrimesLastThree,
|
||||
out SmallPrimesPow2LastThree,
|
||||
out SmallPrimesLastNine,
|
||||
out SmallPrimesPow2LastNine,
|
||||
out SmallPrimesLastOneWithoutLastThree,
|
||||
out SmallPrimesPow2LastOneWithoutLastThree,
|
||||
out SmallPrimesLastSevenWithoutLastThree,
|
||||
out SmallPrimesPow2LastSevenWithoutLastThree,
|
||||
out SmallPrimesLastNineWithoutLastThree,
|
||||
out SmallPrimesPow2LastNineWithoutLastThree);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ushort GetDecimalMask(LastDigit lastDigit)
|
||||
{
|
||||
return lastDigit switch
|
||||
{
|
||||
LastDigit.Seven => DecimalMaskWhenLastIsSeven,
|
||||
LastDigit.Three => DecimalMaskWhenLastIsThree,
|
||||
LastDigit.Nine => DecimalMaskWhenLastIsNine,
|
||||
_ => DecimalMaskOtherwise,
|
||||
};
|
||||
return lastDigit == LastDigit.Seven ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise;
|
||||
}
|
||||
|
||||
private static void BuildSmallPrimes(
|
||||
out uint[] all,
|
||||
out ulong[] allPow2,
|
||||
out uint[] lastOne,
|
||||
out ulong[] lastOnePow2,
|
||||
out uint[] lastSeven,
|
||||
out ulong[] lastSevenPow2,
|
||||
out uint[] lastThree,
|
||||
out ulong[] lastThreePow2,
|
||||
out uint[] lastNine,
|
||||
out ulong[] lastNinePow2,
|
||||
out uint[] lastOneWithoutThree,
|
||||
out ulong[] lastOneWithoutThreePow2,
|
||||
out uint[] lastSevenWithoutThree,
|
||||
out ulong[] lastSevenWithoutThreePow2,
|
||||
out uint[] lastNineWithoutThree,
|
||||
out ulong[] lastNineWithoutThreePow2)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsValidDivisor(
|
||||
byte remainder10,
|
||||
byte remainder8,
|
||||
byte remainder3,
|
||||
byte remainder5,
|
||||
byte remainder7,
|
||||
byte remainder11,
|
||||
ushort decimalMask)
|
||||
{
|
||||
uint target = PerfectNumberConstants.PrimesLimit;
|
||||
int targetInt = checked((int)target);
|
||||
all = new uint[targetInt];
|
||||
allPow2 = new ulong[targetInt];
|
||||
lastOne = new uint[targetInt];
|
||||
lastOnePow2 = new ulong[targetInt];
|
||||
lastSeven = new uint[targetInt];
|
||||
lastSevenPow2 = new ulong[targetInt];
|
||||
lastThree = new uint[targetInt];
|
||||
lastThreePow2 = new ulong[targetInt];
|
||||
lastNine = new uint[targetInt];
|
||||
lastNinePow2 = new ulong[targetInt];
|
||||
|
||||
List<uint> primes = new(targetInt * 4 / 3 + 16);
|
||||
List<uint> lastOneWithoutThreeList = new(targetInt);
|
||||
List<ulong> lastOneWithoutThreePow2List = new(targetInt);
|
||||
List<uint> lastSevenWithoutThreeList = new(targetInt);
|
||||
List<ulong> lastSevenWithoutThreePow2List = new(targetInt);
|
||||
List<uint> lastNineWithoutThreeList = new(targetInt);
|
||||
List<ulong> lastNineWithoutThreePow2List = new(targetInt);
|
||||
|
||||
uint candidate = 2U;
|
||||
int allCount = 0;
|
||||
int lastOneCount = 0;
|
||||
int lastSevenCount = 0;
|
||||
int lastThreeCount = 0;
|
||||
int lastNineCount = 0;
|
||||
|
||||
while (allCount < targetInt ||
|
||||
lastOneCount < targetInt ||
|
||||
lastSevenCount < targetInt ||
|
||||
lastThreeCount < targetInt ||
|
||||
lastNineCount < targetInt)
|
||||
if (((decimalMask >> remainder10) & 1) == 0)
|
||||
{
|
||||
bool isPrime = true;
|
||||
int primesCount = primes.Count;
|
||||
for (int i = 0; i < primesCount; i++)
|
||||
{
|
||||
uint p = primes[i];
|
||||
if (p * p > candidate)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (candidate % p == 0U)
|
||||
{
|
||||
isPrime = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isPrime)
|
||||
{
|
||||
primes.Add(candidate);
|
||||
ulong candidateSquared = checked((ulong)candidate * candidate);
|
||||
if (allCount < targetInt)
|
||||
{
|
||||
all[allCount] = candidate;
|
||||
allPow2[allCount] = candidateSquared;
|
||||
allCount++;
|
||||
}
|
||||
|
||||
if (candidate != 2U)
|
||||
{
|
||||
LastDigit lastDigit = GetLastDigit(candidate);
|
||||
bool allowedForLastOne = IsAllowedForLastOne(candidate, lastDigit);
|
||||
bool allowedForLastSeven = IsAllowedForLastSeven(candidate, lastDigit);
|
||||
bool allowedForLastThree = IsAllowedForLastThree(candidate, lastDigit);
|
||||
bool allowedForLastNine = IsAllowedForLastNine(candidate, lastDigit);
|
||||
bool lastDigitIsThree = lastDigit == LastDigit.Three;
|
||||
|
||||
if (allowedForLastOne && lastOneCount < targetInt)
|
||||
{
|
||||
lastOne[lastOneCount] = candidate;
|
||||
lastOnePow2[lastOneCount] = candidateSquared;
|
||||
lastOneCount++;
|
||||
}
|
||||
|
||||
if (allowedForLastSeven && lastSevenCount < targetInt)
|
||||
{
|
||||
lastSeven[lastSevenCount] = candidate;
|
||||
lastSevenPow2[lastSevenCount] = candidateSquared;
|
||||
lastSevenCount++;
|
||||
}
|
||||
|
||||
if (allowedForLastThree && lastThreeCount < targetInt)
|
||||
{
|
||||
lastThree[lastThreeCount] = candidate;
|
||||
lastThreePow2[lastThreeCount] = candidateSquared;
|
||||
lastThreeCount++;
|
||||
}
|
||||
|
||||
if (allowedForLastNine && lastNineCount < targetInt)
|
||||
{
|
||||
lastNine[lastNineCount] = candidate;
|
||||
lastNinePow2[lastNineCount] = candidateSquared;
|
||||
lastNineCount++;
|
||||
}
|
||||
|
||||
if (!lastDigitIsThree && allowedForLastOne)
|
||||
{
|
||||
lastOneWithoutThreeList.Add(candidate);
|
||||
lastOneWithoutThreePow2List.Add(candidateSquared);
|
||||
}
|
||||
|
||||
if (!lastDigitIsThree && allowedForLastSeven)
|
||||
{
|
||||
lastSevenWithoutThreeList.Add(candidate);
|
||||
lastSevenWithoutThreePow2List.Add(candidateSquared);
|
||||
}
|
||||
|
||||
if (!lastDigitIsThree && allowedForLastNine)
|
||||
{
|
||||
lastNineWithoutThreeList.Add(candidate);
|
||||
lastNineWithoutThreePow2List.Add(candidateSquared);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
candidate = candidate == 2U ? 3U : candidate + 2U;
|
||||
return false;
|
||||
}
|
||||
|
||||
lastOneWithoutThree = lastOneWithoutThreeList.ToArray();
|
||||
lastOneWithoutThreePow2 = lastOneWithoutThreePow2List.ToArray();
|
||||
lastSevenWithoutThree = lastSevenWithoutThreeList.ToArray();
|
||||
lastSevenWithoutThreePow2 = lastSevenWithoutThreePow2List.ToArray();
|
||||
lastNineWithoutThree = lastNineWithoutThreeList.ToArray();
|
||||
lastNineWithoutThreePow2 = lastNineWithoutThreePow2List.ToArray();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static LastDigit GetLastDigit(uint prime)
|
||||
{
|
||||
return (prime % 10U) switch
|
||||
if (remainder8 != 1 && remainder8 != 7)
|
||||
{
|
||||
1U => LastDigit.One,
|
||||
3U => LastDigit.Three,
|
||||
5U => LastDigit.Five,
|
||||
7U => LastDigit.Seven,
|
||||
9U => LastDigit.Nine,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(prime)),
|
||||
};
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsAllowedForLastOne(uint prime, LastDigit lastDigit)
|
||||
{
|
||||
// TODO: Swap the `% 10` usage for ULongExtensions.Mod10 so the hot classification path
|
||||
// reuses the benchmarked residue helper instead of repeated divisions.
|
||||
return lastDigit switch
|
||||
if (remainder3 == 0 || remainder5 == 0 || remainder7 == 0 || remainder11 == 0)
|
||||
{
|
||||
LastDigit.One or LastDigit.Three or LastDigit.Nine => true,
|
||||
LastDigit.Seven => prime == 7U,
|
||||
_ => prime == 11U,
|
||||
};
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsAllowedForLastSeven(uint prime, LastDigit lastDigit)
|
||||
{
|
||||
// TODO: Route this `% 10` classification through ULongExtensions.Mod10 to match the faster
|
||||
// residue helper used elsewhere in the scanner.
|
||||
return lastDigit switch
|
||||
{
|
||||
LastDigit.Three or LastDigit.Seven or LastDigit.Nine => true,
|
||||
_ => prime == 11U,
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsAllowedForLastThree(uint prime, LastDigit lastDigit)
|
||||
{
|
||||
// TODO: Replace the `% 10` residue classification with ULongExtensions.Mod10 once the helper
|
||||
// migrates to UInt32 so we can reuse the optimized decimal residue path everywhere.
|
||||
return lastDigit switch
|
||||
{
|
||||
LastDigit.Three or LastDigit.Seven => true,
|
||||
LastDigit.One => prime == 11U,
|
||||
LastDigit.Nine => prime == 19U,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsAllowedForLastNine(uint prime, LastDigit lastDigit)
|
||||
{
|
||||
// TODO: Replace the `% 10` residue classification with ULongExtensions.Mod10 when the helper
|
||||
// supports UInt32 inputs so the residue tables share the optimized decimal path.
|
||||
return lastDigit switch
|
||||
{
|
||||
LastDigit.Three or LastDigit.Seven or LastDigit.Nine => true,
|
||||
LastDigit.One => prime == 11U,
|
||||
_ => false,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,7 @@ internal struct ExponentRemainderStepperCpu
|
||||
// single-block divisor-cycle snapshot so we can skip the powmod entirely and reuse the
|
||||
// cached Montgomery residue ladder highlighted in MersenneDivisorCycleLengthGpuBenchmarks.
|
||||
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||
_currentMontgomery = _currentMontgomery.MontgomeryMultiplyCpu(multiplier, _modulus, _nPrime);
|
||||
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
||||
PreviousExponent = exponent;
|
||||
return ReduceCurrent();
|
||||
}
|
||||
@@ -92,7 +92,7 @@ internal struct ExponentRemainderStepperCpu
|
||||
// TODO: Reuse the divisor-cycle derived Montgomery delta once the cache exposes single-cycle
|
||||
// lookups so this branch also avoids recomputing powmods when the snapshot lacks the divisor.
|
||||
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
||||
_currentMontgomery = _currentMontgomery.MontgomeryMultiplyCpu(multiplier, _modulus, _nPrime);
|
||||
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
||||
PreviousExponent = exponent;
|
||||
return _currentMontgomery == _montgomeryOne;
|
||||
}
|
||||
@@ -126,7 +126,7 @@ internal struct ExponentRemainderStepperCpu
|
||||
// TODO: Once the divisor-cycle cache exposes a direct Montgomery delta, multiply it here instead
|
||||
// of relying on the caller-provided delta so incremental scans remain in sync with the snapshot
|
||||
// without computing additional cycles or mutating cache state.
|
||||
_currentMontgomery = _currentMontgomery.MontgomeryMultiplyCpu(montgomeryDelta, _modulus, _nPrime);
|
||||
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(montgomeryDelta, _modulus, _nPrime);
|
||||
PreviousExponent = exponent;
|
||||
isUnity = _currentMontgomery == _montgomeryOne;
|
||||
return true;
|
||||
@@ -138,5 +138,5 @@ internal struct ExponentRemainderStepperCpu
|
||||
PreviousExponent = exponent;
|
||||
}
|
||||
|
||||
private ulong ReduceCurrent() => _currentMontgomery.MontgomeryMultiplyCpu(1UL, _modulus, _nPrime);
|
||||
private ulong ReduceCurrent() => _currentMontgomery.MontgomeryMultiply(1UL, _modulus, _nPrime);
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu
|
||||
{
|
||||
public struct AcceleratorPool
|
||||
{
|
||||
internal readonly Context Context;
|
||||
internal readonly Device Device;
|
||||
internal readonly Accelerator[] Accelerators;
|
||||
|
||||
private int _index;
|
||||
|
||||
public int Rent()
|
||||
{
|
||||
Atomic.CompareExchange(ref _index, Capacity, 0);
|
||||
var index = Atomic.Add(ref _index, 1);
|
||||
if (index >= Capacity)
|
||||
{
|
||||
index -= Capacity;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public void Return(int acceleratorIndex)
|
||||
{
|
||||
// Intentionally left empty - there's nothing to do here. We're not really renting anything.
|
||||
}
|
||||
|
||||
public static readonly AcceleratorPool Shared = new(PerfectNumberConstants.RollingAccelerators);
|
||||
|
||||
private readonly int Capacity;
|
||||
|
||||
public AcceleratorPool(int capacity)
|
||||
{
|
||||
Console.WriteLine("Creating accelerators");
|
||||
Capacity = capacity;
|
||||
var context = Context.CreateDefault();
|
||||
var device = context.GetPreferredDevice(false);
|
||||
Context = context;
|
||||
Device = device;
|
||||
int acceleratorsInContext = 0;
|
||||
int acceleratorIndex = 0;
|
||||
Accelerators = [.. Enumerable.Range(0, capacity).Select(_ =>
|
||||
{
|
||||
Console.WriteLine($"Preparing accelerator {acceleratorIndex++}");
|
||||
Accelerator accelerator = device.CreateAccelerator(context);
|
||||
acceleratorsInContext++;
|
||||
if (acceleratorsInContext >= 128)
|
||||
{
|
||||
context = Context.CreateDefault();
|
||||
device = context.GetPreferredDevice(false);
|
||||
acceleratorsInContext = 0;
|
||||
}
|
||||
|
||||
return accelerator;
|
||||
})];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
internal sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
|
||||
{
|
||||
internal static AcceleratorReferenceComparer Instance { get; } = new();
|
||||
|
||||
public bool Equals(Accelerator? x, Accelerator? y) => ReferenceEquals(x, y);
|
||||
|
||||
public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
public static class AcceleratorExtensions
|
||||
{
|
||||
public static MemoryBuffer1D<ulong, Stride1D.Dense> CopySpanToDevice(this Accelerator accelerator, AcceleratorStream stream, in ulong[] span)
|
||||
{
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense>? buffer;
|
||||
buffer = accelerator.Allocate1D(stream, span);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static MemoryBuffer1D<ulong, Stride1D.Dense> CopyUintSpanToDevice(this Accelerator accelerator, AcceleratorStream stream, ReadOnlySpan<uint> span)
|
||||
{
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense>? buffer;
|
||||
buffer = accelerator.Allocate1D<ulong>(span.Length);
|
||||
|
||||
if (!span.IsEmpty)
|
||||
{
|
||||
var converted = new ulong[span.Length];
|
||||
for (int i = 0; i < span.Length; i++)
|
||||
{
|
||||
converted[i] = span[i];
|
||||
}
|
||||
|
||||
buffer.View.CopyFromCPU(stream, converted);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using System.Collections.Concurrent;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
public static class AcceleratorStreamPool
|
||||
{
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
private static readonly SemaphoreSlim[] _locks = new SemaphoreSlim[PerfectNumberConstants.RollingAccelerators];
|
||||
private static readonly ConcurrentQueue<AcceleratorStream>[] _streams = new ConcurrentQueue<AcceleratorStream> [PerfectNumberConstants.RollingAccelerators];
|
||||
|
||||
private static SemaphoreSlim CreateLock() => new(PerfectNumberConstants.ThreadsByAccelerator);
|
||||
|
||||
|
||||
// private static int _rented;
|
||||
|
||||
public static AcceleratorStream Rent(int acceleratorIndex)
|
||||
{
|
||||
var streamLock = _locks[acceleratorIndex];
|
||||
var queue = _streams[acceleratorIndex];
|
||||
|
||||
streamLock.Wait();
|
||||
|
||||
return queue.TryDequeue(out var stream)
|
||||
? stream
|
||||
: _accelerators[acceleratorIndex].CreateStream();
|
||||
}
|
||||
|
||||
public static void Return(int acceleratorIndex, AcceleratorStream stream)
|
||||
{
|
||||
var streamLock = _locks[acceleratorIndex];
|
||||
var queue = _streams[acceleratorIndex];
|
||||
queue.Enqueue(stream);
|
||||
streamLock.Release();
|
||||
}
|
||||
|
||||
public static void WarmUp(int acceleratorIndex)
|
||||
{
|
||||
_streams[acceleratorIndex] = new();
|
||||
_locks[acceleratorIndex] = CreateLock();
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
internal readonly struct HeuristicCombinedGpuTables
|
||||
{
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
|
||||
private static readonly HeuristicCombinedGpuTables[] _sharedTables = new HeuristicCombinedGpuTables[PerfectNumberConstants.RollingAccelerators];
|
||||
|
||||
internal static void WarmUp(int acceleratorIndex, AcceleratorStream stream) => _sharedTables[acceleratorIndex] = new(_accelerators[acceleratorIndex], stream);
|
||||
|
||||
internal static HeuristicCombinedGpuTables GetStaticTables(int acceleratorIndex) => _sharedTables[acceleratorIndex];
|
||||
|
||||
internal HeuristicCombinedGpuTables(Accelerator accelerator, AcceleratorStream stream)
|
||||
{
|
||||
var combinedEnding1 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding1;
|
||||
HeuristicCombinedDivisorsEnding1 = accelerator.CopySpanToDevice(stream, combinedEnding1);
|
||||
var combinedSquaresEnding1 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding1Squares;
|
||||
HeuristicCombinedDivisorSquaresEnding1 = accelerator.CopySpanToDevice(stream, combinedSquaresEnding1);
|
||||
|
||||
var combinedEnding3 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding3;
|
||||
HeuristicCombinedDivisorsEnding3 = accelerator.CopySpanToDevice(stream, combinedEnding3);
|
||||
var combinedSquaresEnding3 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding3Squares;
|
||||
HeuristicCombinedDivisorSquaresEnding3 = accelerator.CopySpanToDevice(stream, combinedSquaresEnding3);
|
||||
|
||||
var combinedEnding7 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding7;
|
||||
HeuristicCombinedDivisorsEnding7 = accelerator.CopySpanToDevice(stream, combinedEnding7);
|
||||
var combinedSquaresEnding7 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding7Squares;
|
||||
HeuristicCombinedDivisorSquaresEnding7 = accelerator.CopySpanToDevice(stream, combinedSquaresEnding7);
|
||||
|
||||
var combinedEnding9 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding9;
|
||||
HeuristicCombinedDivisorsEnding9 = accelerator.CopySpanToDevice(stream, combinedEnding9);
|
||||
var combinedSquaresEnding9 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding9Squares;
|
||||
HeuristicCombinedDivisorSquaresEnding9 = accelerator.CopySpanToDevice(stream, combinedSquaresEnding9);
|
||||
}
|
||||
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding1;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding1;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding3;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding3;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding7;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding7;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding9;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding9;
|
||||
|
||||
internal HeuristicCombinedGpuViews CreateViews() => new(
|
||||
HeuristicCombinedDivisorsEnding1.View,
|
||||
HeuristicCombinedDivisorsEnding3.View,
|
||||
HeuristicCombinedDivisorsEnding7.View,
|
||||
HeuristicCombinedDivisorsEnding9.View,
|
||||
HeuristicCombinedDivisorSquaresEnding1.View,
|
||||
HeuristicCombinedDivisorSquaresEnding3.View,
|
||||
HeuristicCombinedDivisorSquaresEnding7.View,
|
||||
HeuristicCombinedDivisorSquaresEnding9.View);
|
||||
|
||||
internal void Dispose()
|
||||
{
|
||||
HeuristicCombinedDivisorsEnding1.Dispose();
|
||||
HeuristicCombinedDivisorSquaresEnding1.Dispose();
|
||||
HeuristicCombinedDivisorsEnding3.Dispose();
|
||||
HeuristicCombinedDivisorSquaresEnding3.Dispose();
|
||||
HeuristicCombinedDivisorsEnding7.Dispose();
|
||||
HeuristicCombinedDivisorSquaresEnding7.Dispose();
|
||||
HeuristicCombinedDivisorsEnding9.Dispose();
|
||||
HeuristicCombinedDivisorSquaresEnding9.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
public readonly struct HeuristicCombinedGpuViews(
|
||||
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding1,
|
||||
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding3,
|
||||
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding7,
|
||||
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding9,
|
||||
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorSquaresEnding1,
|
||||
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorSquaresEnding3,
|
||||
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorSquaresEnding7,
|
||||
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorSquaresEnding9)
|
||||
{
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding1 = combinedDivisorsEnding1;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding3 = combinedDivisorsEnding3;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding7 = combinedDivisorsEnding7;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding9 = combinedDivisorsEnding9;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding1 = combinedDivisorSquaresEnding1;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding3 = combinedDivisorSquaresEnding3;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding7 = combinedDivisorSquaresEnding7;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding9 = combinedDivisorSquaresEnding9;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ArrayView1D<ulong, Stride1D.Dense> SelectDivisors(byte ending) => ending switch
|
||||
{
|
||||
1 => CombinedDivisorsEnding1,
|
||||
3 => CombinedDivisorsEnding3,
|
||||
7 => CombinedDivisorsEnding7,
|
||||
9 => CombinedDivisorsEnding9,
|
||||
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
|
||||
};
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ArrayView1D<ulong, Stride1D.Dense> SelectDivisorSquares(byte ending) => ending switch
|
||||
{
|
||||
1 => CombinedDivisorSquaresEnding1,
|
||||
3 => CombinedDivisorSquaresEnding3,
|
||||
7 => CombinedDivisorSquaresEnding7,
|
||||
9 => CombinedDivisorSquaresEnding9,
|
||||
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
|
||||
};
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
internal sealed class HeuristicGroupABGpuTables
|
||||
{
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
|
||||
private static readonly HeuristicGroupABGpuTables[] _sharedTables = new HeuristicGroupABGpuTables[PerfectNumberConstants.RollingAccelerators];
|
||||
|
||||
private static readonly SemaphoreSlim[] _locks = [..Enumerable.Range(1, PerfectNumberConstants.RollingAccelerators).Select(_ => new SemaphoreSlim(1))];
|
||||
|
||||
internal static HeuristicGroupABGpuTables EnsureStaticTables(int acceleratorIndex, AcceleratorStream stream)
|
||||
{
|
||||
var @lock = _locks[acceleratorIndex];
|
||||
@lock.Wait();
|
||||
if (_sharedTables[acceleratorIndex] is {} existing)
|
||||
{
|
||||
@lock.Release();
|
||||
return existing;
|
||||
}
|
||||
|
||||
var accelerator = _accelerators[acceleratorIndex];
|
||||
existing = new(accelerator, stream);
|
||||
_sharedTables[acceleratorIndex] = existing;
|
||||
@lock.Release();
|
||||
return existing;
|
||||
}
|
||||
|
||||
internal HeuristicGroupABGpuTables(Accelerator accelerator, AcceleratorStream stream)
|
||||
{
|
||||
var heuristicGroupA = HeuristicPrimeSieves.GroupADivisorsStorage;
|
||||
HeuristicGroupADivisors = accelerator.CopySpanToDevice(stream, heuristicGroupA);
|
||||
|
||||
var heuristicGroupASquares = HeuristicPrimeSieves.GroupADivisorSquaresStorage;
|
||||
HeuristicGroupADivisorSquares = accelerator.CopySpanToDevice(stream, heuristicGroupASquares);
|
||||
|
||||
HeuristicGroupBDivisorsEnding1 = accelerator.CopyUintSpanToDevice(stream, DivisorGenerator.SmallPrimesLastOneWithoutLastThree);
|
||||
HeuristicGroupBDivisorSquaresEnding1 = accelerator.CopySpanToDevice(stream, DivisorGenerator.SmallPrimesPow2LastOneWithoutLastThree);
|
||||
|
||||
HeuristicGroupBDivisorsEnding7 = accelerator.CopyUintSpanToDevice(stream, DivisorGenerator.SmallPrimesLastSevenWithoutLastThree);
|
||||
HeuristicGroupBDivisorSquaresEnding7 = accelerator.CopySpanToDevice(stream, DivisorGenerator.SmallPrimesPow2LastSevenWithoutLastThree);
|
||||
|
||||
HeuristicGroupBDivisorsEnding9 = accelerator.CopyUintSpanToDevice(stream, DivisorGenerator.SmallPrimesLastNineWithoutLastThree);
|
||||
HeuristicGroupBDivisorSquaresEnding9 = accelerator.CopySpanToDevice(stream, DivisorGenerator.SmallPrimesPow2LastNineWithoutLastThree);
|
||||
}
|
||||
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisors;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisorSquares;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding1;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding1;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding7;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding7;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding9;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding9;
|
||||
|
||||
internal HeuristicGroupABGpuViews CreateViews() => new(
|
||||
HeuristicGroupADivisors.View,
|
||||
HeuristicGroupADivisorSquares.View,
|
||||
HeuristicGroupBDivisorsEnding1.View,
|
||||
HeuristicGroupBDivisorSquaresEnding1.View,
|
||||
HeuristicGroupBDivisorsEnding7.View,
|
||||
HeuristicGroupBDivisorSquaresEnding7.View,
|
||||
HeuristicGroupBDivisorsEnding9.View,
|
||||
HeuristicGroupBDivisorSquaresEnding9.View);
|
||||
|
||||
internal void Dispose()
|
||||
{
|
||||
HeuristicGroupADivisors.Dispose();
|
||||
HeuristicGroupADivisorSquares.Dispose();
|
||||
HeuristicGroupBDivisorsEnding1.Dispose();
|
||||
HeuristicGroupBDivisorSquaresEnding1.Dispose();
|
||||
HeuristicGroupBDivisorsEnding7.Dispose();
|
||||
HeuristicGroupBDivisorSquaresEnding7.Dispose();
|
||||
HeuristicGroupBDivisorsEnding9.Dispose();
|
||||
HeuristicGroupBDivisorSquaresEnding9.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
public readonly struct HeuristicGroupABGpuViews(
|
||||
ArrayView1D<ulong, Stride1D.Dense> groupADivisors,
|
||||
ArrayView1D<ulong, Stride1D.Dense> groupADivisorSquares,
|
||||
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding1,
|
||||
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorSquaresEnding1,
|
||||
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding7,
|
||||
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorSquaresEnding7,
|
||||
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding9,
|
||||
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorSquaresEnding9)
|
||||
{
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupADivisors = groupADivisors;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupADivisorSquares = groupADivisorSquares;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding1 = groupBDivisorsEnding1;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorSquaresEnding1 = groupBDivisorSquaresEnding1;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding7 = groupBDivisorsEnding7;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorSquaresEnding7 = groupBDivisorSquaresEnding7;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding9 = groupBDivisorsEnding9;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorSquaresEnding9 = groupBDivisorSquaresEnding9;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ArrayView1D<ulong, Stride1D.Dense> SelectGroupB(byte ending) => ending switch
|
||||
{
|
||||
1 => GroupBDivisorsEnding1,
|
||||
7 => GroupBDivisorsEnding7,
|
||||
9 => GroupBDivisorsEnding9,
|
||||
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
|
||||
};
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ArrayView1D<ulong, Stride1D.Dense> SelectGroupBSquares(byte ending) => ending switch
|
||||
{
|
||||
1 => GroupBDivisorSquaresEnding1,
|
||||
7 => GroupBDivisorSquaresEnding7,
|
||||
9 => GroupBDivisorSquaresEnding9,
|
||||
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
|
||||
};
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ArrayView1D<ulong, Stride1D.Dense> SelectDivisors(HeuristicGpuDivisorTableKind kind) => kind switch
|
||||
{
|
||||
HeuristicGpuDivisorTableKind.GroupA => GroupADivisors,
|
||||
HeuristicGpuDivisorTableKind.GroupBEnding1 => GroupBDivisorsEnding1,
|
||||
HeuristicGpuDivisorTableKind.GroupBEnding7 => GroupBDivisorsEnding7,
|
||||
HeuristicGpuDivisorTableKind.GroupBEnding9 => GroupBDivisorsEnding9,
|
||||
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
|
||||
};
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ArrayView1D<ulong, Stride1D.Dense> SelectDivisorSquares(HeuristicGpuDivisorTableKind kind) => kind switch
|
||||
{
|
||||
HeuristicGpuDivisorTableKind.GroupA => GroupADivisorSquares,
|
||||
HeuristicGpuDivisorTableKind.GroupBEnding1 => GroupBDivisorSquaresEnding1,
|
||||
HeuristicGpuDivisorTableKind.GroupBEnding7 => GroupBDivisorSquaresEnding7,
|
||||
HeuristicGpuDivisorTableKind.GroupBEnding9 => GroupBDivisorSquaresEnding9,
|
||||
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
|
||||
};
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
internal sealed class HeuristicGroupABPrimeTesterAccelerator
|
||||
{
|
||||
#region Pool
|
||||
|
||||
[ThreadStatic]
|
||||
private static Queue<HeuristicGroupABPrimeTesterAccelerator>? Pool;
|
||||
|
||||
internal static void WarmUp()
|
||||
{
|
||||
Accelerator[]? accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
int acceleratorCount = accelerators.Length;
|
||||
for (var i = 0; i < acceleratorCount; i++)
|
||||
{
|
||||
Console.WriteLine($"Preparing heuristic accelerator {i}...");
|
||||
var accelerator = accelerators[i];
|
||||
AcceleratorStream stream = accelerator.CreateStream();
|
||||
_ = HeuristicGroupABGpuTables.EnsureStaticTables(i, stream);
|
||||
// _ = GpuKernelPool.GetOrAddKernels(accelerator, stream, KernelType.None);
|
||||
// KernelContainer kernels = GpuKernelPool.GetOrAddKernels(accelerator, stream);
|
||||
// GpuStaticTableInitializer.EnsureStaticTables(accelerator, kernels, stream);
|
||||
stream.Synchronize();
|
||||
stream.Dispose();
|
||||
Console.WriteLine($"Heuristic accelerator {i} is ready");
|
||||
}
|
||||
}
|
||||
|
||||
internal static HeuristicGroupABPrimeTesterAccelerator Rent(int minBufferCapacity = 1)
|
||||
{
|
||||
var pool = Pool ??= new();
|
||||
if (!pool.TryDequeue(out var lease))
|
||||
{
|
||||
lease = new HeuristicGroupABPrimeTesterAccelerator(minBufferCapacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
lease.EnsureCapacity(minBufferCapacity);
|
||||
}
|
||||
|
||||
return lease;
|
||||
}
|
||||
|
||||
internal static void Return(HeuristicGroupABPrimeTesterAccelerator lease)
|
||||
{
|
||||
var pool = Pool ??= new();
|
||||
pool.Enqueue(lease);
|
||||
}
|
||||
|
||||
internal static void DisposeAll()
|
||||
{
|
||||
if (Pool != null)
|
||||
{
|
||||
while (Pool.TryDequeue(out var lease))
|
||||
{
|
||||
lease.DisposeResources();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Clear()
|
||||
{
|
||||
if (Pool != null)
|
||||
{
|
||||
var retained = new List<HeuristicGroupABPrimeTesterAccelerator>();
|
||||
while (Pool.TryDequeue(out var lease))
|
||||
{
|
||||
lease.DisposeResources();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public readonly HeuristicGroupABGpuViews DivisorTables;
|
||||
internal readonly Context Context;
|
||||
public readonly Accelerator Accelerator;
|
||||
|
||||
[ThreadStatic]
|
||||
public static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>>? SmallPrimeSieveKernel;
|
||||
|
||||
internal static Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>> GetSmallPrimeSieveKernel(Accelerator accelerator)
|
||||
{
|
||||
var pool = SmallPrimeSieveKernel ??= [];
|
||||
if (pool.TryGetValue(accelerator, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
cached = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterKernels.SmallPrimeSieveKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>>();
|
||||
pool[accelerator] = cached;
|
||||
return cached;
|
||||
}
|
||||
|
||||
public readonly Kernel HeuristicTrialDivisionKernel;
|
||||
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense>? Input = null;
|
||||
public MemoryBuffer1D<byte, Stride1D.Dense>? Output = null;
|
||||
public MemoryBuffer1D<int, Stride1D.Dense>? HeuristicFlag = null!;
|
||||
public int BufferCapacity;
|
||||
|
||||
private HeuristicGroupABGpuTables _sharedTables;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisors => _sharedTables.HeuristicGroupADivisors;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisorSquares => _sharedTables.HeuristicGroupADivisorSquares;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding1 => _sharedTables.HeuristicGroupBDivisorsEnding1;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding1 => _sharedTables.HeuristicGroupBDivisorSquaresEnding1;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding7 => _sharedTables.HeuristicGroupBDivisorsEnding7;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding7 => _sharedTables.HeuristicGroupBDivisorSquaresEnding7;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding9 => _sharedTables.HeuristicGroupBDivisorsEnding9;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding9 => _sharedTables.HeuristicGroupBDivisorSquaresEnding9;
|
||||
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
internal readonly int AcceleratorIndex;
|
||||
|
||||
internal HeuristicGroupABPrimeTesterAccelerator(int minBufferCapacity)
|
||||
{
|
||||
var acceleratorIndex = AcceleratorPool.Shared.Rent();
|
||||
AcceleratorIndex = acceleratorIndex;
|
||||
Accelerator accelerator = _accelerators[acceleratorIndex];
|
||||
Context = accelerator.Context;
|
||||
|
||||
Accelerator = accelerator;
|
||||
EnsureCapacity(minBufferCapacity);
|
||||
|
||||
HeuristicTrialDivisionKernel = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<int>, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGroupABGpuViews>(PrimeTesterKernels.HeuristicTrialGroupABDivisionKernel));
|
||||
|
||||
AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex);
|
||||
|
||||
// GpuStaticTableInitializer.EnsureStaticTables(accelerator, kernels, stream);
|
||||
|
||||
_sharedTables = HeuristicGroupABGpuTables.EnsureStaticTables(acceleratorIndex, stream);
|
||||
DivisorTables = _sharedTables.CreateViews();
|
||||
|
||||
stream.Synchronize();
|
||||
AcceleratorStreamPool.Return(acceleratorIndex, stream);
|
||||
}
|
||||
|
||||
public void EnsureCapacity(int minCapacity)
|
||||
{
|
||||
if (minCapacity <= BufferCapacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Input?.Dispose();
|
||||
Output?.Dispose();
|
||||
HeuristicFlag?.Dispose();
|
||||
|
||||
Input = Accelerator.Allocate1D<ulong>(minCapacity);
|
||||
Output = Accelerator.Allocate1D<byte>(minCapacity);
|
||||
HeuristicFlag = Accelerator.Allocate1D<int>(minCapacity);
|
||||
|
||||
BufferCapacity = minCapacity;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Return(this);
|
||||
}
|
||||
|
||||
internal void DisposeResources()
|
||||
{
|
||||
Input?.Dispose();
|
||||
Output?.Dispose();
|
||||
HeuristicFlag?.Dispose();
|
||||
// ReleaseSharedTables(_sharedTables);
|
||||
// These resources are shared between GPU leases
|
||||
// Accelerator.Dispose();
|
||||
// AcceleratorContext.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
internal readonly struct LastDigitGpuTables
|
||||
{
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
private static readonly LastDigitGpuTables[] _sharedTables = new LastDigitGpuTables[PerfectNumberConstants.RollingAccelerators];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static LastDigitGpuTables GetStaticTables(int acceleratorIndex) => _sharedTables[acceleratorIndex];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void WarmUp(int acceleratorIndex, AcceleratorStream stream) => _sharedTables[acceleratorIndex] = new LastDigitGpuTables(_accelerators[acceleratorIndex], stream);
|
||||
|
||||
internal LastDigitGpuTables(Accelerator accelerator, AcceleratorStream stream)
|
||||
{
|
||||
var primesLastOne = DivisorGenerator.SmallPrimesLastOne;
|
||||
var primesLastSeven = DivisorGenerator.SmallPrimesLastSeven;
|
||||
var primesLastThree = DivisorGenerator.SmallPrimesLastThree;
|
||||
var primesLastNine = DivisorGenerator.SmallPrimesLastNine;
|
||||
var primesPow2LastOne = DivisorGenerator.SmallPrimesPow2LastOne;
|
||||
var primesPow2LastSeven = DivisorGenerator.SmallPrimesPow2LastSeven;
|
||||
var primesPow2LastThree = DivisorGenerator.SmallPrimesPow2LastThree;
|
||||
var primesPow2LastNine = DivisorGenerator.SmallPrimesPow2LastNine;
|
||||
DevicePrimesLastOne = accelerator.Allocate1D(stream, primesLastOne);
|
||||
DevicePrimesLastSeven = accelerator.Allocate1D(stream, primesLastSeven);
|
||||
DevicePrimesLastThree = accelerator.Allocate1D(stream, primesLastThree);
|
||||
DevicePrimesLastNine = accelerator.Allocate1D(stream, primesLastNine);
|
||||
DevicePrimesPow2LastOne = accelerator.Allocate1D(stream, primesPow2LastOne);
|
||||
DevicePrimesPow2LastSeven = accelerator.Allocate1D(stream, primesPow2LastSeven);
|
||||
DevicePrimesPow2LastThree = accelerator.Allocate1D(stream, primesPow2LastThree);
|
||||
DevicePrimesPow2LastNine = accelerator.Allocate1D(stream, primesPow2LastNine);
|
||||
}
|
||||
|
||||
internal readonly MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastOne;
|
||||
internal readonly MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastSeven;
|
||||
internal readonly MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastThree;
|
||||
internal readonly MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastNine;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastOne;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastSeven;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastThree;
|
||||
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastNine;
|
||||
|
||||
internal void Dispose()
|
||||
{
|
||||
DevicePrimesLastOne.Dispose();
|
||||
DevicePrimesLastSeven.Dispose();
|
||||
DevicePrimesLastThree.Dispose();
|
||||
DevicePrimesLastNine.Dispose();
|
||||
DevicePrimesPow2LastOne.Dispose();
|
||||
DevicePrimesPow2LastSeven.Dispose();
|
||||
DevicePrimesPow2LastThree.Dispose();
|
||||
DevicePrimesPow2LastNine.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core.Gpu.Kernels;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
public sealed class PrimeOrderCalculatorAccelerator
|
||||
{
|
||||
#region Pool
|
||||
|
||||
[ThreadStatic]
|
||||
private static Queue<PrimeOrderCalculatorAccelerator>? _pool;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static PrimeOrderCalculatorAccelerator Rent(int primeTesterCapacity)
|
||||
{
|
||||
var pool = _pool ??= [];
|
||||
if (!pool.TryDequeue(out var gpu))
|
||||
{
|
||||
int acceleratorIndex = AcceleratorPool.Shared.Rent();
|
||||
gpu = new(acceleratorIndex, PerfectNumberConstants.DefaultFactorsBuffer, primeTesterCapacity, PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount, PerfectNumberConstants.DefaultSpecialMaxFactorCapacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
gpu.EnsureCapacity(PerfectNumberConstants.DefaultFactorsBuffer, primeTesterCapacity);
|
||||
}
|
||||
|
||||
return gpu;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Return(PrimeOrderCalculatorAccelerator gpu) => _pool!.Enqueue(gpu);
|
||||
|
||||
internal static void Clear()
|
||||
{
|
||||
Queue<PrimeOrderCalculatorAccelerator>? pool = _pool;
|
||||
if (pool != null)
|
||||
{
|
||||
while (pool.TryDequeue(out var gpu))
|
||||
{
|
||||
gpu.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void DisposeAll()
|
||||
{
|
||||
Queue<PrimeOrderCalculatorAccelerator>? pool = _pool;
|
||||
if (pool != null)
|
||||
{
|
||||
while (pool.TryDequeue(out var gpu))
|
||||
{
|
||||
gpu.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void WarmUp()
|
||||
{
|
||||
Accelerator[] accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
int acceleratorCount = accelerators.Length;
|
||||
for (var i = 0; i < acceleratorCount; i++)
|
||||
{
|
||||
Console.WriteLine($"Preparing accelerator {i}...");
|
||||
var accelerator = accelerators[i];
|
||||
AcceleratorStreamPool.WarmUp(i);
|
||||
// Don't take this from the pool as quick uploads of data to the accelerator consumes much of GPU's memory and throws.
|
||||
AcceleratorStream stream = accelerator.CreateStream();
|
||||
// TODO: Review which tables are needed when final execution path is defined.
|
||||
LastDigitGpuTables.WarmUp(i, stream);
|
||||
HeuristicCombinedGpuTables.WarmUp(i, stream);
|
||||
// SmallPrimeFactorGpuTables.WarmUp(i, stream);
|
||||
// SharedHeuristicGpuTables.EnsureStaticTables(accelerator, stream);
|
||||
// _ = GpuKernelPool.GetOrAddKernels(accelerator, stream, KernelType.None);
|
||||
// KernelContainer kernels = GpuKernelPool.GetOrAddKernels(accelerator, stream);
|
||||
// GpuStaticTableInitializer.EnsureStaticTables(accelerator, kernels, stream);
|
||||
stream.Synchronize();
|
||||
stream.Dispose();
|
||||
accelerator.Synchronize();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region Static Tables
|
||||
|
||||
public readonly ArrayView1D<uint, Stride1D.Dense> DevicePrimesLastOne;
|
||||
public readonly ArrayView1D<uint, Stride1D.Dense> DevicePrimesLastSeven;
|
||||
public readonly ArrayView1D<uint, Stride1D.Dense> DevicePrimesLastThree;
|
||||
public readonly ArrayView1D<uint, Stride1D.Dense> DevicePrimesLastNine;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> DevicePrimesPow2LastOne;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> DevicePrimesPow2LastSeven;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> DevicePrimesPow2LastThree;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> DevicePrimesPow2LastNine;
|
||||
|
||||
public readonly HeuristicCombinedGpuViews DivisorTables;
|
||||
|
||||
public readonly ArrayView1D<uint, Stride1D.Dense> SmallPrimeFactorPrimes;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> SmallPrimeFactorSquares;
|
||||
|
||||
#endregion
|
||||
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
|
||||
public readonly Accelerator Accelerator;
|
||||
public readonly int AcceleratorIndex;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense> Input;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense> OutputUlong;
|
||||
public MemoryBuffer1D<byte, Stride1D.Dense> OutputByte;
|
||||
public MemoryBuffer1D<int, Stride1D.Dense> OutputInt;
|
||||
public MemoryBuffer1D<KeyValuePair<ulong, int>, Stride1D.Dense> Pow2ModEntriesToTestOnDevice;
|
||||
public readonly MemoryBuffer1D<int, Stride1D.Dense> SmallPrimeFactorCountSlot;
|
||||
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> SpecialMaxResult;
|
||||
|
||||
public Dictionary<ulong, int> Pow2ModEntriesToTestOnHost = [];
|
||||
|
||||
public readonly Action<AcceleratorStream, int, ulong, ArrayView1D<KeyValuePair<ulong, int>, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> CheckFactorsKernelLauncher;
|
||||
|
||||
public readonly Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> ConvertToStandardKernelLauncher;
|
||||
|
||||
public readonly Action<AcceleratorStream, Index1D, byte, ArrayView<int>, ulong, ulong, HeuristicCombinedGpuViews> HeuristicCombinedTrialDivisionKernelLauncher;
|
||||
public readonly Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> KeepMontgomeryKernelLauncher;
|
||||
public readonly Action<AcceleratorStream, ulong, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> PollardRhoKernelLauncher;
|
||||
public readonly Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>> Pow2ModWideKernelLauncher;
|
||||
public readonly Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<byte>> SharesFactorKernelLauncher;
|
||||
public readonly Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> SmallPrimeFactorKernelLauncher;
|
||||
public readonly Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>> SmallPrimeSieveKernelLauncher;
|
||||
public readonly Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> SpecialMaxKernelLauncher;
|
||||
|
||||
public void EnsureCapacity(int factorsCount, int primeTesterCapacity)
|
||||
{
|
||||
Accelerator accelerator = Accelerator;
|
||||
|
||||
MemoryBuffer1D<KeyValuePair<ulong, int>, Stride1D.Dense> pow2ModEntriesToTestOnDevice = Pow2ModEntriesToTestOnDevice;
|
||||
if (pow2ModEntriesToTestOnDevice.Length < factorsCount)
|
||||
{
|
||||
lock (accelerator)
|
||||
{
|
||||
pow2ModEntriesToTestOnDevice.Dispose();
|
||||
Pow2ModEntriesToTestOnDevice = accelerator.Allocate1D<KeyValuePair<ulong, int>>(factorsCount);
|
||||
}
|
||||
}
|
||||
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense> input = Input;
|
||||
if (input.Length < primeTesterCapacity)
|
||||
{
|
||||
lock (accelerator)
|
||||
{
|
||||
input.Dispose();
|
||||
Input = accelerator.Allocate1D<ulong>(primeTesterCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
MemoryBuffer1D<byte, Stride1D.Dense> outputByte = OutputByte;
|
||||
if (outputByte.Length < primeTesterCapacity)
|
||||
{
|
||||
lock (accelerator)
|
||||
{
|
||||
outputByte.Dispose();
|
||||
OutputByte = accelerator.Allocate1D<byte>(primeTesterCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
MemoryBuffer1D<int, Stride1D.Dense> outputInt = OutputInt;
|
||||
if (outputInt.Length < primeTesterCapacity)
|
||||
{
|
||||
lock (accelerator)
|
||||
{
|
||||
outputInt.Dispose();
|
||||
OutputInt = accelerator.Allocate1D<int>(primeTesterCapacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EnsureSmallPrimeFactorSlotsCapacity(int newSize)
|
||||
{
|
||||
// Console.WriteLine($"Resizing GPU scratch buffer from pool ({buffer.SmallPrimeFactorPrimeSlots.Length} / {smallPrimeFactorSlotCount}), ({buffer.SpecialMaxFactors.Length}/{specialMaxFactorCapacity})");
|
||||
var accelerator = Accelerator;
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense> outputUlong = OutputUlong;
|
||||
if (outputUlong.Length < newSize)
|
||||
{
|
||||
lock (accelerator)
|
||||
{
|
||||
outputUlong.Dispose();
|
||||
OutputUlong = accelerator.Allocate1D<ulong>(newSize);
|
||||
}
|
||||
}
|
||||
|
||||
MemoryBuffer1D<int, Stride1D.Dense> outputInt = OutputInt;
|
||||
if (outputInt.Length < newSize)
|
||||
{
|
||||
lock (accelerator)
|
||||
{
|
||||
outputInt.Dispose();
|
||||
OutputInt = accelerator.Allocate1D<int>(newSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EnsureSpecialMaxFactorsCapacity(int newSize)
|
||||
{
|
||||
var accelerator = Accelerator;
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense> input = Input;
|
||||
if (input.Length < newSize)
|
||||
{
|
||||
lock (accelerator)
|
||||
{
|
||||
input.Dispose();
|
||||
Input = accelerator.Allocate1D<ulong>(newSize);
|
||||
}
|
||||
}
|
||||
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense> outputUlong = OutputUlong;
|
||||
if (outputUlong.Length < newSize)
|
||||
{
|
||||
lock (accelerator)
|
||||
{
|
||||
outputUlong.Dispose();
|
||||
OutputUlong = accelerator.Allocate1D<ulong>(newSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PrimeOrderCalculatorAccelerator(int acceleratorIndex, int factorsCount, int primeTesterCapacity, int smallPrimeFactorSlotCount, int specialMaxFactorCapacity)
|
||||
{
|
||||
var accelerator = _accelerators[acceleratorIndex];
|
||||
Accelerator = accelerator;
|
||||
AcceleratorIndex = acceleratorIndex;
|
||||
lock (accelerator)
|
||||
{
|
||||
Input = accelerator.Allocate1D<ulong>(Math.Max(specialMaxFactorCapacity, primeTesterCapacity));
|
||||
OutputByte = Accelerator.Allocate1D<byte>(primeTesterCapacity);
|
||||
OutputInt = Accelerator.Allocate1D<int>(Math.Max(primeTesterCapacity, smallPrimeFactorSlotCount));
|
||||
OutputUlong = accelerator.Allocate1D<ulong>(Math.Max(specialMaxFactorCapacity, smallPrimeFactorSlotCount));
|
||||
Pow2ModEntriesToTestOnDevice = accelerator.Allocate1D<KeyValuePair<ulong, int>>(factorsCount);
|
||||
SmallPrimeFactorCountSlot = accelerator.Allocate1D<int>(1);
|
||||
SpecialMaxResult = accelerator.Allocate1D<ulong>(1);
|
||||
|
||||
LastDigitGpuTables lastDigitSharedTables = LastDigitGpuTables.GetStaticTables(acceleratorIndex);
|
||||
DevicePrimesLastOne = lastDigitSharedTables.DevicePrimesLastOne.View;
|
||||
DevicePrimesLastSeven = lastDigitSharedTables.DevicePrimesLastSeven.View;
|
||||
DevicePrimesLastThree = lastDigitSharedTables.DevicePrimesLastThree.View;
|
||||
DevicePrimesLastNine = lastDigitSharedTables.DevicePrimesLastNine.View;
|
||||
DevicePrimesPow2LastOne = lastDigitSharedTables.DevicePrimesPow2LastOne.View;
|
||||
DevicePrimesPow2LastSeven = lastDigitSharedTables.DevicePrimesPow2LastSeven.View;
|
||||
DevicePrimesPow2LastThree = lastDigitSharedTables.DevicePrimesPow2LastThree.View;
|
||||
DevicePrimesPow2LastNine = lastDigitSharedTables.DevicePrimesPow2LastNine.View;
|
||||
|
||||
DivisorTables = HeuristicCombinedGpuTables
|
||||
.GetStaticTables(acceleratorIndex)
|
||||
.CreateViews();
|
||||
|
||||
var smallPrimeFactorTables = SmallPrimeFactorGpuTables.GetStaticTables(acceleratorIndex);
|
||||
|
||||
if (smallPrimeFactorTables.HasValue)
|
||||
{
|
||||
SmallPrimeFactorGpuTables tables = smallPrimeFactorTables.Value;
|
||||
SmallPrimeFactorPrimes = tables.Primes.View;
|
||||
SmallPrimeFactorSquares = tables.Squares.View;
|
||||
}
|
||||
|
||||
CheckFactorsKernelLauncher = KernelUtil.GetKernel(accelerator.LoadStreamKernel<int, ulong, ArrayView1D<KeyValuePair<ulong, int>, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>(PrimeOrderGpuHeuristics.CheckFactorsKernel)).CreateLauncherDelegate<Action<AcceleratorStream, int, ulong, ArrayView1D<KeyValuePair<ulong, int>, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
|
||||
ConvertToStandardKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>(Pow2MontgomeryKernels.Pow2MontgomeryKernelConvertToStandard)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
|
||||
HeuristicCombinedTrialDivisionKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, byte, ArrayView<int>, ulong, ulong, HeuristicCombinedGpuViews>(PrimeTesterKernels.HeuristicTrialCombinedDivisionKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, byte, ArrayView<int>, ulong, ulong, HeuristicCombinedGpuViews>>();
|
||||
|
||||
KeepMontgomeryKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>(Pow2MontgomeryKernels.Pow2MontgomeryKernelKeepMontgomery)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
|
||||
PollardRhoKernelLauncher = KernelUtil.GetKernel(accelerator.LoadStreamKernel<ulong, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2MontgomeryKernels.TryPollardRhoKernel)).CreateLauncherDelegate<Action<AcceleratorStream, ulong, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
|
||||
Pow2ModWideKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>>(PrimeOrderGpuHeuristics.Pow2ModKernelWide)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>>>();
|
||||
|
||||
SharesFactorKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterKernels.SharesFactorKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<byte>>>();
|
||||
|
||||
SmallPrimeFactorKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(SmallPrimeFactorKernels.SmallPrimeFactorKernelScan)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
|
||||
SmallPrimeSieveKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterKernels.SmallPrimeSieveKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>>();
|
||||
|
||||
SpecialMaxKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(PrimeOrderGpuHeuristics.EvaluateSpecialMaxCandidatesKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Input.Dispose();
|
||||
OutputByte.Dispose();
|
||||
OutputInt.Dispose();
|
||||
OutputUlong.Dispose();
|
||||
Pow2ModEntriesToTestOnDevice.Dispose();
|
||||
SmallPrimeFactorCountSlot.Dispose();
|
||||
SpecialMaxResult.Dispose();
|
||||
|
||||
// These resources are shared between GPU leases
|
||||
// Stream.Dispose();
|
||||
// Accelerator.Dispose();
|
||||
// Context.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public static class ThreadStaticDeterministicRandomGpu
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static DeterministicRandomGpu? _exclusive;
|
||||
internal static DeterministicRandomGpu Exclusive => _exclusive ?? new DeterministicRandomGpu();
|
||||
}
|
||||
|
||||
internal struct DeterministicRandomGpu
|
||||
{
|
||||
private static long s_seedCounter;
|
||||
|
||||
private ulong _state;
|
||||
public readonly ulong State
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
}
|
||||
|
||||
public DeterministicRandomGpu()
|
||||
{
|
||||
_state = CreateThreadState();
|
||||
}
|
||||
|
||||
public DeterministicRandomGpu(ulong initialState)
|
||||
{
|
||||
_state = initialState;
|
||||
}
|
||||
|
||||
public ulong NextUInt64()
|
||||
{
|
||||
ulong state = _state;
|
||||
state ^= state >> 12;
|
||||
state ^= state << 25;
|
||||
state ^= state >> 27;
|
||||
_state = state;
|
||||
return state * 2685821657736338717UL;
|
||||
}
|
||||
|
||||
public GpuUInt128 NextUInt128()
|
||||
{
|
||||
GpuUInt128 high = (GpuUInt128)NextUInt64();
|
||||
ReadOnlyGpuUInt128 low = (ReadOnlyGpuUInt128)NextUInt64();
|
||||
high.ShiftLeft(64);
|
||||
high.Add(low);
|
||||
return high;
|
||||
}
|
||||
|
||||
public void SetState(ulong state)
|
||||
{
|
||||
_state = state;
|
||||
}
|
||||
|
||||
private static ulong CreateThreadState()
|
||||
{
|
||||
ulong seed = (ulong)Atomic.Add(ref s_seedCounter, 1);
|
||||
seed += 0x9E3779B97F4A7C15UL;
|
||||
seed = (seed ^ (seed >> 30)) * 0xBF58476D1CE4E5B9UL;
|
||||
seed = (seed ^ (seed >> 27)) * 0x94D049BB133111EBUL;
|
||||
seed ^= seed >> 31;
|
||||
if (seed == 0UL)
|
||||
{
|
||||
seed = 0x9E3779B97F4A7C15UL;
|
||||
}
|
||||
|
||||
return seed;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
@@ -23,14 +24,17 @@ internal struct ExponentRemainderStepperGpu
|
||||
_currentResidue = GpuUInt128.One;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool MatchesDivisor(in GpuDivisorPartialData divisor) => _modulus == divisor.Modulus;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MatchesDivisor(in GpuDivisorPartialData divisor)
|
||||
{
|
||||
return _modulus == divisor.Modulus;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the cached residue so the stepper can be reused for another divisor.
|
||||
/// Callers must reinitialize the stepper before consuming any residues after a reset.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
/// <summary>
|
||||
/// Clears the cached residue so the stepper can be reused for another divisor.
|
||||
/// Callers must reinitialize the stepper before consuming any residues after a reset.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Reset()
|
||||
{
|
||||
PreviousExponent = 0UL;
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
using System.Collections.Concurrent;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public static class GpuContextPool
|
||||
{
|
||||
private static readonly bool PoolingEnabled = true;
|
||||
// The pool intentionally skips pre-loading ProcessEightBitWindows kernels so accelerator initialization
|
||||
// stays stable; GpuKernelPool JIT-compiles them on first use alongside the small-cycle uploads.
|
||||
// Default device preference for generic GPU kernels (prime scans, NTT, etc.)
|
||||
public static bool ForceCpu { get; set; } = false;
|
||||
|
||||
internal sealed class PooledContext
|
||||
{
|
||||
private bool disposed;
|
||||
|
||||
public Context Context { get; }
|
||||
public Accelerator Accelerator { get; }
|
||||
public bool IsCpu { get; }
|
||||
public object ExecutionLock { get; } = new();
|
||||
|
||||
public PooledContext(bool preferCpu)
|
||||
{
|
||||
Context = Context.CreateDefault();
|
||||
Accelerator = Context.GetPreferredDevice(preferCpu).CreateAccelerator(Context);
|
||||
IsCpu = Accelerator.AcceleratorType == AcceleratorType.CPU;
|
||||
// NOTE: Avoid loading/compiling any kernel here to prevent implicit
|
||||
// CL stream/queue creation during accelerator construction.
|
||||
// Some OpenCL drivers are fragile when a queue is created immediately
|
||||
// after device init. Real kernels will be JIT-loaded at first use.
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure all cached GPU buffers for this accelerator are released.
|
||||
NttGpuMath.ClearCaches(Accelerator);
|
||||
// Release PrimeTester GPU state for this accelerator (kernel/device primes)
|
||||
// Clear any per-accelerator cached resources to avoid releasing
|
||||
// after the accelerator is destroyed.
|
||||
PrimeTester.ClearGpuCaches(Accelerator);
|
||||
|
||||
Accelerator.Dispose();
|
||||
Context.Dispose();
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ConcurrentQueue<PooledContext> CpuPool = new();
|
||||
private static readonly ConcurrentQueue<PooledContext> GpuPool = new();
|
||||
private static readonly object CreationLock = new();
|
||||
|
||||
public static GpuContextLease Rent()
|
||||
{
|
||||
return RentPreferred(ForceCpu);
|
||||
}
|
||||
|
||||
// Allows callers to choose CPU/GPU per use-case, decoupled from ForceCpu.
|
||||
public static GpuContextLease RentPreferred(bool preferCpu)
|
||||
{
|
||||
if (PoolingEnabled)
|
||||
{
|
||||
if (preferCpu && CpuPool.TryDequeue(out var cpu))
|
||||
{
|
||||
return new GpuContextLease(cpu, CreationLock);
|
||||
}
|
||||
|
||||
if (!preferCpu && GpuPool.TryDequeue(out var gpu))
|
||||
{
|
||||
return new GpuContextLease(gpu, CreationLock);
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize accelerator creation to avoid concurrent CL device/stream setup across threads.
|
||||
lock (CreationLock)
|
||||
{
|
||||
return new GpuContextLease(new PooledContext(preferCpu), CreationLock);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DisposeAll()
|
||||
{
|
||||
if (!PoolingEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (CpuPool.TryDequeue(out var cpu))
|
||||
{
|
||||
cpu.Dispose();
|
||||
}
|
||||
|
||||
while (GpuPool.TryDequeue(out var gpu))
|
||||
{
|
||||
gpu.Dispose();
|
||||
}
|
||||
|
||||
PrimeTester.DisposeGpuContexts();
|
||||
}
|
||||
|
||||
private static void Return(PooledContext ctx)
|
||||
{
|
||||
if (PoolingEnabled)
|
||||
{
|
||||
ctx.Accelerator.Synchronize();
|
||||
if (ctx.IsCpu)
|
||||
{
|
||||
CpuPool.Enqueue(ctx);
|
||||
}
|
||||
else
|
||||
{
|
||||
GpuPool.Enqueue(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
lock (CreationLock)
|
||||
{
|
||||
ctx.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct GpuContextLease
|
||||
{
|
||||
private readonly PooledContext _ctx;
|
||||
private bool disposed;
|
||||
|
||||
internal GpuContextLease(PooledContext ctx, object kernelInitLock)
|
||||
{
|
||||
_ctx = ctx;
|
||||
KernelInitLock = kernelInitLock;
|
||||
}
|
||||
|
||||
public Context Context => _ctx.Context;
|
||||
|
||||
public Accelerator Accelerator => _ctx.Accelerator;
|
||||
|
||||
public object KernelInitLock { get; }
|
||||
|
||||
public object ExecutionLock => _ctx.ExecutionLock;
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Return(_ctx);
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
using System.Collections.Concurrent;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using static PerfectNumbers.Core.Gpu.GpuContextPool;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public sealed class GpuKernelLease
|
||||
{
|
||||
private static readonly ConcurrentQueue<GpuKernelLease> Pool = new();
|
||||
|
||||
private IDisposable? _limiter;
|
||||
private GpuContextLease? _gpu;
|
||||
private KernelContainer? _kernels;
|
||||
private AcceleratorStream? _stream;
|
||||
private object? _executionLock;
|
||||
private bool _disposed;
|
||||
|
||||
private GpuKernelLease()
|
||||
{
|
||||
}
|
||||
|
||||
internal static GpuKernelLease Rent(IDisposable limiter, GpuContextLease gpu, KernelContainer kernels)
|
||||
{
|
||||
if (!Pool.TryDequeue(out var lease))
|
||||
{
|
||||
lease = new GpuKernelLease();
|
||||
}
|
||||
|
||||
lease._limiter = limiter;
|
||||
lease._gpu = gpu;
|
||||
lease._kernels = kernels;
|
||||
lease._stream = null;
|
||||
lease._executionLock = gpu.ExecutionLock;
|
||||
lease._disposed = false;
|
||||
return lease;
|
||||
}
|
||||
|
||||
public Accelerator Accelerator => _gpu?.Accelerator ?? throw new NullReferenceException("GPU context is already released");
|
||||
|
||||
public AcceleratorStream Stream
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_stream is { } stream)
|
||||
{
|
||||
return stream;
|
||||
}
|
||||
|
||||
// It feels that we can combine these into 1 return statement, but that increases the max stack frame size to 3 from 2
|
||||
stream = Accelerator.CreateStream();
|
||||
_stream = stream;
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
||||
public ExecutionScope EnterExecutionScope() => new ExecutionScope(_executionLock);
|
||||
|
||||
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>> OrderKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
var accel = Accelerator; // avoid capturing 'this' in lambda
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels!.Order, () =>
|
||||
{
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernels.OrderKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels!.Pow2Mod, () =>
|
||||
{
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels!.Incremental, () =>
|
||||
{
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalOrderKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels!.IncrementalOrder, () =>
|
||||
{
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalOrderKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModOrderKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels!.Pow2ModOrder, () =>
|
||||
{
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModOrderKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> SmallPrimeFactorKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels!.SmallPrimeFactor, () =>
|
||||
{
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(SmallPrimeFactorKernels.SmallPrimeFactorKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_stream?.Dispose();
|
||||
_stream = null;
|
||||
|
||||
_gpu?.Dispose();
|
||||
_gpu = null;
|
||||
|
||||
_executionLock = null;
|
||||
|
||||
_limiter?.Dispose();
|
||||
_limiter = null;
|
||||
}
|
||||
|
||||
_kernels = null;
|
||||
_disposed = true;
|
||||
Pool.Enqueue(this);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public readonly struct ExecutionScope
|
||||
{
|
||||
private readonly object? _lock;
|
||||
|
||||
public ExecutionScope(object? sync)
|
||||
{
|
||||
_lock = sync;
|
||||
if (_lock is not null)
|
||||
{
|
||||
Monitor.Enter(_lock);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_lock is not null)
|
||||
{
|
||||
Monitor.Exit(_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,232 +1,305 @@
|
||||
using System.Collections.Concurrent;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
using static PerfectNumbers.Core.Gpu.GpuContextPool;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public readonly struct ResidueAutomatonArgs
|
||||
{
|
||||
public readonly ulong Q0M10;
|
||||
public readonly ulong Step10;
|
||||
public readonly ulong Q0M8;
|
||||
public readonly ulong Step8;
|
||||
public readonly ulong Q0M3;
|
||||
public readonly ulong Step3;
|
||||
public readonly ulong Q0M5;
|
||||
public readonly ulong Step5;
|
||||
|
||||
public ResidueAutomatonArgs(ulong q0m10, ulong step10, ulong q0m8, ulong step8, ulong q0m3, ulong step3, ulong q0m5, ulong step5)
|
||||
{
|
||||
Q0M10 = q0m10; Step10 = step10; Q0M8 = q0m8; Step8 = step8; Q0M3 = q0m3; Step3 = step3; Q0M5 = q0m5; Step5 = step5;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class KernelContainer
|
||||
{
|
||||
// Serializes first-time initialization of kernels/buffers per accelerator.
|
||||
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>? Order;
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>? Incremental;
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>,
|
||||
ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>,
|
||||
ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2Mod;
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? IncrementalOrder;
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2ModOrder;
|
||||
public Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? SmallPrimeFactor;
|
||||
|
||||
// Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length.
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallCycles;
|
||||
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimeFactorsPrimes;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorsSquares;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorPrimeSlots;
|
||||
public MemoryBuffer1D<int, Stride1D.Dense>? SmallPrimeFactorExponentSlots;
|
||||
public MemoryBuffer1D<int, Stride1D.Dense>? SmallPrimeFactorCountSlot;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorRemainingSlot;
|
||||
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastOne;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastOne;
|
||||
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastSeven;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastSeven;
|
||||
|
||||
public static T InitOnce<T>(ref T? slot, Func<T> factory) where T : class
|
||||
{
|
||||
var current = Volatile.Read(ref slot);
|
||||
if (current is not null)
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
current = Volatile.Read(ref slot);
|
||||
if (current is null)
|
||||
{
|
||||
current = factory();
|
||||
Volatile.Write(ref slot, current);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ResiduePrimeViews(
|
||||
ArrayView1D<uint, Stride1D.Dense> lastOne,
|
||||
ArrayView1D<uint, Stride1D.Dense> lastSeven,
|
||||
ArrayView1D<ulong, Stride1D.Dense> lastOnePow2,
|
||||
ArrayView1D<ulong, Stride1D.Dense> lastSevenPow2)
|
||||
{
|
||||
public readonly ArrayView1D<uint, Stride1D.Dense> LastOne = lastOne;
|
||||
public readonly ArrayView1D<uint, Stride1D.Dense> LastSeven = lastSeven;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> LastOnePow2 = lastOnePow2;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> LastSevenPow2 = lastSevenPow2;
|
||||
}
|
||||
|
||||
public readonly struct SmallPrimeFactorTables
|
||||
{
|
||||
public readonly MemoryBuffer1D<uint, Stride1D.Dense> Primes;
|
||||
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> Squares;
|
||||
public readonly int Count;
|
||||
|
||||
public SmallPrimeFactorTables(
|
||||
MemoryBuffer1D<uint, Stride1D.Dense> primes,
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense> squares,
|
||||
int count)
|
||||
{
|
||||
Primes = primes;
|
||||
Squares = squares;
|
||||
Count = count;
|
||||
}
|
||||
|
||||
public ArrayView1D<uint, Stride1D.Dense> PrimesView => Primes.View;
|
||||
public ArrayView1D<ulong, Stride1D.Dense> SquaresView => Squares.View;
|
||||
}
|
||||
|
||||
public readonly struct SmallPrimeFactorScratch
|
||||
{
|
||||
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> PrimeSlots;
|
||||
public readonly MemoryBuffer1D<int, Stride1D.Dense> ExponentSlots;
|
||||
public readonly MemoryBuffer1D<int, Stride1D.Dense> CountSlot;
|
||||
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> RemainingSlot;
|
||||
|
||||
public SmallPrimeFactorScratch(
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense> primeSlots,
|
||||
MemoryBuffer1D<int, Stride1D.Dense> exponentSlots,
|
||||
MemoryBuffer1D<int, Stride1D.Dense> countSlot,
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense> remainingSlot)
|
||||
{
|
||||
PrimeSlots = primeSlots;
|
||||
ExponentSlots = exponentSlots;
|
||||
CountSlot = countSlot;
|
||||
RemainingSlot = remainingSlot;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
PrimeSlots.MemSetToZero();
|
||||
ExponentSlots.MemSetToZero();
|
||||
CountSlot.MemSetToZero();
|
||||
RemainingSlot.MemSetToZero();
|
||||
}
|
||||
}
|
||||
|
||||
public class GpuKernelPool
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static KernelContainer[]? _pool;
|
||||
private static readonly ConcurrentDictionary<Accelerator, KernelContainer> KernelCache = new(); // TODO: Replace this concurrent map with a simple accelerator-indexed lookup once kernel launchers are prewarmed during startup so we can drop the thread-safe wrapper entirely.
|
||||
|
||||
public static T InitOnce<T>(ref T? target, Func<T> valueFactory) where T : class
|
||||
{
|
||||
if (target is { } value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
private static KernelContainer GetKernels(Accelerator accelerator)
|
||||
{
|
||||
return KernelCache.GetOrAdd(accelerator, _ => new KernelContainer());
|
||||
}
|
||||
|
||||
var newValue = valueFactory();
|
||||
return Interlocked.CompareExchange(ref target, newValue, null) ?? newValue;
|
||||
}
|
||||
// Ensures the small cycles table is uploaded to the device for the given accelerator.
|
||||
// Returns the ArrayView to pass into kernels (when kernels are extended to accept it).
|
||||
public static ArrayView1D<ulong, Stride1D.Dense> EnsureSmallCyclesOnDevice(Accelerator accelerator)
|
||||
{
|
||||
var kernels = GetKernels(accelerator);
|
||||
if (kernels.SmallCycles is { } buffer)
|
||||
{
|
||||
return buffer.View;
|
||||
}
|
||||
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
// Ensure single upload per accelerator even if multiple threads race here.
|
||||
lock (kernels) // TODO: Remove this lock by pre-uploading the immutable small-cycle snapshot during initialization; once no mutation happens at runtime, the pool must expose a simple reference without synchronization.
|
||||
{
|
||||
if (kernels.SmallCycles is { } existing)
|
||||
{
|
||||
return existing.View;
|
||||
}
|
||||
|
||||
var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); // TODO: Preload this device buffer during startup and keep it immutable so we can delete the lock above in favor of the preloaded snapshot.
|
||||
var device = accelerator.Allocate1D<ulong>(host.Length);
|
||||
device.View.CopyFromCPU(host);
|
||||
kernels.SmallCycles = device;
|
||||
return device.View;
|
||||
}
|
||||
}
|
||||
|
||||
internal static KernelContainer GetOrAddKernels(int acceleratorIndex, AcceleratorStream stream, KernelType kernelType)
|
||||
{
|
||||
var pool = _pool ??= [];
|
||||
if (pool[acceleratorIndex] is not { } kernels)
|
||||
{
|
||||
kernels = new();
|
||||
}
|
||||
public static ResiduePrimeViews EnsureSmallPrimesOnDevice(Accelerator accelerator)
|
||||
{
|
||||
var kernels = GetKernels(accelerator);
|
||||
if (kernels.SmallPrimesLastOne is { } lastOne &&
|
||||
kernels.SmallPrimesLastSeven is { } lastSeven &&
|
||||
kernels.SmallPrimesPow2LastOne is { } lastOnePow2 &&
|
||||
kernels.SmallPrimesPow2LastSeven is { } lastSevenPow2)
|
||||
{
|
||||
return new ResiduePrimeViews(lastOne.View, lastSeven.View, lastOnePow2.View, lastSevenPow2.View);
|
||||
}
|
||||
|
||||
Accelerator accelerator = _accelerators[acceleratorIndex];
|
||||
if (kernelType.HasFlag(KernelType.OrderKernelScan))
|
||||
{
|
||||
InitOrderKernelScan(accelerator, kernels);
|
||||
}
|
||||
lock (kernels) // TODO: Inline these small-prime uploads into startup initialization alongside the small-cycle snapshot so we can drop runtime locking and keep the GPU pool free of synchronization.
|
||||
{
|
||||
if (kernels.SmallPrimesLastOne is { } existingLastOne &&
|
||||
kernels.SmallPrimesLastSeven is { } existingLastSeven &&
|
||||
kernels.SmallPrimesPow2LastOne is { } existingLastOnePow2 &&
|
||||
kernels.SmallPrimesPow2LastSeven is { } existingLastSevenPow2)
|
||||
{
|
||||
return new ResiduePrimeViews(existingLastOne.View, existingLastSeven.View, existingLastOnePow2.View, existingLastSevenPow2.View);
|
||||
}
|
||||
|
||||
if (kernelType.HasFlag(KernelType.Pow2ModKernelScan))
|
||||
{
|
||||
InitPow2ModKernelScan(accelerator, kernels);
|
||||
}
|
||||
var hostLastOne = PrimesGenerator.SmallPrimesLastOne;
|
||||
var hostLastSeven = PrimesGenerator.SmallPrimesLastSeven;
|
||||
var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne;
|
||||
var hostLastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven;
|
||||
|
||||
if (kernelType.HasFlag(KernelType.IncrementalKernelScan))
|
||||
{
|
||||
InitIncrementalKernelScan(accelerator, kernels);
|
||||
}
|
||||
var deviceLastOne = accelerator.Allocate1D<uint>(hostLastOne.Length);
|
||||
deviceLastOne.View.CopyFromCPU(hostLastOne);
|
||||
var deviceLastSeven = accelerator.Allocate1D<uint>(hostLastSeven.Length);
|
||||
deviceLastSeven.View.CopyFromCPU(hostLastSeven);
|
||||
var deviceLastOnePow2 = accelerator.Allocate1D<ulong>(hostLastOnePow2.Length);
|
||||
deviceLastOnePow2.View.CopyFromCPU(hostLastOnePow2);
|
||||
var deviceLastSevenPow2 = accelerator.Allocate1D<ulong>(hostLastSevenPow2.Length);
|
||||
deviceLastSevenPow2.View.CopyFromCPU(hostLastSevenPow2);
|
||||
|
||||
if (kernelType.HasFlag(KernelType.IncrementalOrderKernelScan))
|
||||
{
|
||||
IncrementalOrderKernelScan(accelerator, kernels);
|
||||
}
|
||||
kernels.SmallPrimesLastOne = deviceLastOne;
|
||||
kernels.SmallPrimesLastSeven = deviceLastSeven;
|
||||
kernels.SmallPrimesPow2LastOne = deviceLastOnePow2;
|
||||
kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2;
|
||||
|
||||
if (kernelType.HasFlag(KernelType.Pow2ModOrderKernelScan))
|
||||
{
|
||||
InitPow2ModOrderKernelScan(accelerator, kernels);
|
||||
}
|
||||
return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View);
|
||||
}
|
||||
}
|
||||
|
||||
PreloadStaticTables(accelerator, kernels, stream, kernelType);
|
||||
public static SmallPrimeFactorTables EnsureSmallPrimeFactorTables(Accelerator accelerator)
|
||||
{
|
||||
var kernels = GetKernels(accelerator);
|
||||
if (kernels.SmallPrimeFactorsPrimes is { } primeBuffer && kernels.SmallPrimeFactorsSquares is { } squareBuffer)
|
||||
{
|
||||
return new SmallPrimeFactorTables(primeBuffer, squareBuffer, (int)primeBuffer.Length);
|
||||
}
|
||||
|
||||
pool[acceleratorIndex] = kernels;
|
||||
return kernels;
|
||||
lock (kernels)
|
||||
{
|
||||
if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares)
|
||||
{
|
||||
return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.Length);
|
||||
}
|
||||
|
||||
static void InitOrderKernelScan(Accelerator accelerator, KernelContainer kernels)
|
||||
{
|
||||
InitOnce(ref kernels.Order, () =>
|
||||
{
|
||||
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernels.OrderKernelScan);
|
||||
var hostPrimes = PrimesGenerator.SmallPrimes;
|
||||
var hostSquares = PrimesGenerator.SmallPrimesPow2;
|
||||
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
var devicePrimes = accelerator.Allocate1D<uint>(hostPrimes.Length);
|
||||
devicePrimes.View.CopyFromCPU(hostPrimes);
|
||||
var deviceSquares = accelerator.Allocate1D<ulong>(hostSquares.Length);
|
||||
deviceSquares.View.CopyFromCPU(hostSquares);
|
||||
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>>();
|
||||
});
|
||||
}
|
||||
kernels.SmallPrimeFactorsPrimes = devicePrimes;
|
||||
kernels.SmallPrimeFactorsSquares = deviceSquares;
|
||||
|
||||
static void InitPow2ModKernelScan(Accelerator accelerator, KernelContainer kernels)
|
||||
{
|
||||
InitOnce(ref kernels.Pow2Mod, () =>
|
||||
{
|
||||
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModKernelScan);
|
||||
return new SmallPrimeFactorTables(devicePrimes, deviceSquares, hostPrimes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
public static SmallPrimeFactorScratch EnsureSmallPrimeFactorScratch(Accelerator accelerator, int slotCount)
|
||||
{
|
||||
var kernels = GetKernels(accelerator);
|
||||
lock (kernels)
|
||||
{
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense>? primeSlots = kernels.SmallPrimeFactorPrimeSlots;
|
||||
if (primeSlots is null || primeSlots.Length < slotCount)
|
||||
{
|
||||
primeSlots?.Dispose();
|
||||
primeSlots = accelerator.Allocate1D<ulong>(slotCount);
|
||||
kernels.SmallPrimeFactorPrimeSlots = primeSlots;
|
||||
}
|
||||
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
MemoryBuffer1D<int, Stride1D.Dense>? exponentSlots = kernels.SmallPrimeFactorExponentSlots;
|
||||
if (exponentSlots is null || exponentSlots.Length < slotCount)
|
||||
{
|
||||
exponentSlots?.Dispose();
|
||||
exponentSlots = accelerator.Allocate1D<int>(slotCount);
|
||||
kernels.SmallPrimeFactorExponentSlots = exponentSlots;
|
||||
}
|
||||
|
||||
static void InitIncrementalKernelScan(Accelerator accelerator, KernelContainer kernels)
|
||||
{
|
||||
InitOnce(ref kernels.Incremental, () =>
|
||||
{
|
||||
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalKernelScan);
|
||||
MemoryBuffer1D<int, Stride1D.Dense>? countSlot = kernels.SmallPrimeFactorCountSlot;
|
||||
if (countSlot is null)
|
||||
{
|
||||
countSlot = accelerator.Allocate1D<int>(1);
|
||||
kernels.SmallPrimeFactorCountSlot = countSlot;
|
||||
}
|
||||
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense>? remainingSlot = kernels.SmallPrimeFactorRemainingSlot;
|
||||
if (remainingSlot is null)
|
||||
{
|
||||
remainingSlot = accelerator.Allocate1D<ulong>(1);
|
||||
kernels.SmallPrimeFactorRemainingSlot = remainingSlot;
|
||||
}
|
||||
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
return new SmallPrimeFactorScratch(primeSlots!, exponentSlots!, countSlot!, remainingSlot!);
|
||||
}
|
||||
}
|
||||
|
||||
static void IncrementalOrderKernelScan(Accelerator accelerator, KernelContainer kernels)
|
||||
{
|
||||
InitOnce(ref kernels.IncrementalOrder, () =>
|
||||
{
|
||||
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalOrderKernelScan);
|
||||
public static GpuKernelLease GetKernel(bool useGpuOrder)
|
||||
{
|
||||
var limiter = GpuPrimeWorkLimiter.Acquire();
|
||||
var gpu = RentPreferred(preferCpu: !useGpuOrder);
|
||||
var accelerator = gpu.Accelerator;
|
||||
var kernels = GetKernels(accelerator);
|
||||
return GpuKernelLease.Rent(limiter, gpu, kernels);
|
||||
}
|
||||
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
/// <summary>
|
||||
/// Runs a GPU action with an acquired accelerator and stream.
|
||||
/// </summary>
|
||||
/// <param name="action">Action to run with (Accelerator, Stream).</param>
|
||||
public static void Run(Action<Accelerator, AcceleratorStream> action)
|
||||
{
|
||||
var lease = GetKernel(useGpuOrder: true);
|
||||
var execution = lease.EnterExecutionScope();
|
||||
var accelerator = lease.Accelerator;
|
||||
var stream = accelerator.CreateStream();
|
||||
action(accelerator, stream);
|
||||
stream.Dispose();
|
||||
execution.Dispose();
|
||||
lease.Dispose();
|
||||
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte,
|
||||
ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
|
||||
static void InitPow2ModOrderKernelScan(Accelerator accelerator, KernelContainer kernels)
|
||||
{
|
||||
InitOnce(ref kernels.Pow2ModOrder, () =>
|
||||
{
|
||||
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModOrderKernelScan);
|
||||
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
internal static void PreloadStaticTables(Accelerator accelerator, KernelContainer kernels, AcceleratorStream stream, KernelType kernelType)
|
||||
{
|
||||
if (kernelType.HasFlag(KernelType.SmallCycles))
|
||||
{
|
||||
EnsureSmallCyclesOnDevice(accelerator, kernels, stream);
|
||||
}
|
||||
|
||||
if (kernelType.HasFlag(KernelType.SmallPrimes))
|
||||
{
|
||||
EnsureSmallPrimesOnDevice(accelerator, kernels, stream);
|
||||
}
|
||||
}
|
||||
|
||||
public static ArrayView1D<ulong, Stride1D.Dense> GetSmallCyclesOnDevice(KernelContainer kernels) => kernels.SmallCycles!;
|
||||
|
||||
// Ensures the small cycles table is uploaded to the device for the given accelerator.
|
||||
// Returns the ArrayView to pass into kernels (when kernels are extended to accept it).
|
||||
private static void EnsureSmallCyclesOnDevice(Accelerator accelerator, KernelContainer kernels, AcceleratorStream stream)
|
||||
{
|
||||
if (kernels.SmallCycles is { })
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense>? device;
|
||||
|
||||
lock (accelerator)
|
||||
{
|
||||
device = accelerator.Allocate1D<ulong>(host.Length);
|
||||
}
|
||||
|
||||
device.View.CopyFromCPU(stream, host);
|
||||
kernels.SmallCycles = device;
|
||||
}
|
||||
|
||||
public static ResiduePrimeViews GetSmallPrimesOnDevice(KernelContainer kernels) => new(
|
||||
kernels.SmallPrimesLastOne!,
|
||||
kernels.SmallPrimesLastSeven!,
|
||||
kernels.SmallPrimesPow2LastOne!,
|
||||
kernels.SmallPrimesPow2LastSeven!
|
||||
);
|
||||
|
||||
private static void EnsureSmallPrimesOnDevice(Accelerator accelerator, KernelContainer kernels, AcceleratorStream stream)
|
||||
{
|
||||
if (kernels.SmallPrimesLastOne is { } &&
|
||||
kernels.SmallPrimesLastSeven is { } &&
|
||||
kernels.SmallPrimesPow2LastOne is { } &&
|
||||
kernels.SmallPrimesPow2LastSeven is { })
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (kernels.SmallPrimesLastOne is { } &&
|
||||
kernels.SmallPrimesLastSeven is { } &&
|
||||
kernels.SmallPrimesPow2LastOne is { } &&
|
||||
kernels.SmallPrimesPow2LastSeven is { })
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var hostLastOne = PrimesGenerator.SmallPrimesLastOne;
|
||||
var hostLastSeven = PrimesGenerator.SmallPrimesLastSeven;
|
||||
var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne;
|
||||
var hostLastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven;
|
||||
|
||||
MemoryBuffer1D<uint, Stride1D.Dense>? deviceLastOne;
|
||||
MemoryBuffer1D<uint, Stride1D.Dense>? deviceLastSeven;
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense>? deviceLastOnePow2;
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense>? deviceLastSevenPow2;
|
||||
|
||||
lock (accelerator)
|
||||
{
|
||||
deviceLastOne = accelerator.Allocate1D<uint>(hostLastOne.Length);
|
||||
deviceLastSeven = accelerator.Allocate1D<uint>(hostLastSeven.Length);
|
||||
deviceLastOnePow2 = accelerator.Allocate1D<ulong>(hostLastOnePow2.Length);
|
||||
deviceLastSevenPow2 = accelerator.Allocate1D<ulong>(hostLastSevenPow2.Length);
|
||||
}
|
||||
|
||||
deviceLastOne.View.CopyFromCPU(stream, hostLastOne);
|
||||
deviceLastSeven.View.CopyFromCPU(stream, hostLastSeven);
|
||||
deviceLastOnePow2.View.CopyFromCPU(stream, hostLastOnePow2);
|
||||
deviceLastSevenPow2.View.CopyFromCPU(stream, hostLastSevenPow2);
|
||||
kernels.SmallPrimesLastOne = deviceLastOne;
|
||||
kernels.SmallPrimesLastSeven = deviceLastSeven;
|
||||
kernels.SmallPrimesPow2LastOne = deviceLastOnePow2;
|
||||
kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a GPU action with an acquired accelerator and stream.
|
||||
/// </summary>
|
||||
/// <param name="action">Action to run with (Accelerator, Stream).</param>
|
||||
public static void Run(Action<Accelerator, AcceleratorStream> action)
|
||||
{
|
||||
// GpuPrimeWorkLimiter.Acquire();
|
||||
var acceleratorIndex = AcceleratorPool.Shared.Rent();
|
||||
var accelerator = _accelerators[acceleratorIndex];
|
||||
var stream = AcceleratorStreamPool.Rent(acceleratorIndex);
|
||||
action(accelerator, stream);
|
||||
stream.Synchronize();
|
||||
stream.Dispose();
|
||||
accelerator.Dispose();
|
||||
// GpuPrimeWorkLimiter.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,29 +2,60 @@ namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public static class GpuPrimeWorkLimiter
|
||||
{
|
||||
private static SemaphoreSlim _semaphore = new(1, int.MaxValue);
|
||||
private static int _currentLimit = 1;
|
||||
private static SemaphoreSlim _semaphore = new(1, int.MaxValue);
|
||||
private static readonly object _lock = new();
|
||||
private static int _currentLimit = 1;
|
||||
|
||||
public static void Acquire() { } // => _semaphore.Wait();
|
||||
public static void Release() { }// => _semaphore.Release();
|
||||
|
||||
public static void SetLimit(int value)
|
||||
public static IDisposable Acquire()
|
||||
{
|
||||
if (value <= 0)
|
||||
// TODO: Inline the pooled limiter guard from the GpuLimiterThroughputBenchmarks so prime-sieve
|
||||
// GPU jobs stop allocating Releaser instances on every acquisition.
|
||||
var sem = _semaphore;
|
||||
sem.Wait();
|
||||
return new Releaser(sem);
|
||||
}
|
||||
|
||||
public static void SetLimit(int value)
|
||||
{
|
||||
if (value < 1)
|
||||
{
|
||||
value = 1;
|
||||
}
|
||||
|
||||
if (value == _currentLimit)
|
||||
lock (_lock)
|
||||
{
|
||||
return;
|
||||
if (value == _currentLimit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Switch to a shared limiter implementation with GpuWorkLimiter so we can coordinate CPU/GPU prime work using
|
||||
// the same semaphore pool without reallocating per adjustment.
|
||||
_semaphore = new SemaphoreSlim(value, value);
|
||||
_currentLimit = value;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Releaser : IDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim _sem;
|
||||
private bool _disposed;
|
||||
|
||||
public Releaser(SemaphoreSlim sem)
|
||||
{
|
||||
_sem = sem;
|
||||
}
|
||||
|
||||
// TODO: Switch to a shared limiter implementation with GpuWorkLimiter so we can coordinate CPU/GPU prime work using
|
||||
// the same semaphore pool without reallocating per adjustment.
|
||||
var previous = _semaphore;
|
||||
_semaphore = new SemaphoreSlim(value, value);
|
||||
_currentLimit = value;
|
||||
previous.Dispose();
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_sem.Release();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,13 +261,13 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
public override readonly int GetHashCode() => HashCode.Combine(High, Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(in ReadOnlyGpuUInt128 other) => Add(other.High, other.Low);
|
||||
public void Add(in ReadOnlyGpuUInt128 other) => AddInternal(other.High, other.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(GpuUInt128 other) => Add(other.High, other.Low);
|
||||
public void Add(GpuUInt128 other) => AddInternal(other.High, other.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(ulong otherHigh, ulong otherLow)
|
||||
private void AddInternal(ulong otherHigh, ulong otherLow)
|
||||
{
|
||||
ulong originalLow = Low;
|
||||
// We're reusing originalLow for carry to avoid an extra local.
|
||||
@@ -316,7 +316,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
return;
|
||||
}
|
||||
|
||||
Add(valueHigh, valueLow);
|
||||
AddInternal(valueHigh, valueLow);
|
||||
if (CompareTo(in modulus) >= 0)
|
||||
{
|
||||
Sub(in modulus);
|
||||
@@ -589,6 +589,9 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 BinaryGcd(GpuUInt128 u, GpuUInt128 v)
|
||||
{
|
||||
// TODO: Replace this scalar binary GCD with the branchless reduction from
|
||||
// GpuUInt128BinaryGcdBenchmarks so CPU fallbacks stay aligned with the GPU kernel
|
||||
// performance when resolving large divisor residues.
|
||||
if (u.IsZero)
|
||||
{
|
||||
return v;
|
||||
@@ -599,50 +602,26 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
return u;
|
||||
}
|
||||
|
||||
ulong combinedLow = u.Low | v.Low;
|
||||
int shift;
|
||||
if (combinedLow != 0UL)
|
||||
{
|
||||
shift = BitOperations.TrailingZeroCount(combinedLow);
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong combinedHigh = u.High | v.High;
|
||||
shift = 64 + BitOperations.TrailingZeroCount(combinedHigh);
|
||||
}
|
||||
|
||||
int shift = TrailingZeroCount(new GpuUInt128(u.High | v.High, u.Low | v.Low));
|
||||
int zu = TrailingZeroCount(u);
|
||||
u >>= zu;
|
||||
|
||||
GpuUInt128 currentV = v;
|
||||
while (true)
|
||||
do
|
||||
{
|
||||
int zv = TrailingZeroCount(currentV);
|
||||
currentV >>= zv;
|
||||
|
||||
bool swap = u > currentV;
|
||||
GpuUInt128 min = ConditionalSelect(u, currentV, swap);
|
||||
GpuUInt128 max = ConditionalSelect(currentV, u, swap);
|
||||
max.Sub(min);
|
||||
|
||||
if (max.IsZero)
|
||||
int zv = TrailingZeroCount(v);
|
||||
v >>= zv;
|
||||
if (u > v)
|
||||
{
|
||||
return min << shift;
|
||||
(u, v) = (v, u);
|
||||
}
|
||||
|
||||
u = min;
|
||||
currentV = max;
|
||||
v -= u;
|
||||
}
|
||||
while (!v.IsZero);
|
||||
|
||||
return u << shift;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static GpuUInt128 ConditionalSelect(in GpuUInt128 left, in GpuUInt128 right, bool useRight)
|
||||
{
|
||||
ulong mask = useRight ? ulong.MaxValue : 0UL;
|
||||
return new GpuUInt128(
|
||||
left.High ^ ((left.High ^ right.High) & mask),
|
||||
left.Low ^ ((left.Low ^ right.Low) & mask));
|
||||
}
|
||||
|
||||
public void Sub(ulong value)
|
||||
{
|
||||
@@ -1550,15 +1529,6 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
high = hi;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Pow2()
|
||||
{
|
||||
ulong operand = Low;
|
||||
Low = operand * operand;
|
||||
ulong highProduct = operand * High;
|
||||
High = highProduct + MulHigh(operand, operand);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO: Check if the TODO below is still relevant.
|
||||
|
||||
@@ -6,14 +6,14 @@ public static class GpuWorkLimiter
|
||||
private static readonly object _lock = new();
|
||||
private static int _currentLimit = 1;
|
||||
|
||||
public static Lease Acquire()
|
||||
public static IDisposable Acquire()
|
||||
{
|
||||
// TODO: Replace the per-call Lease allocation with the pooled struct-based guard from the
|
||||
// TODO: Replace the per-call Releaser allocation with the pooled struct-based guard from the
|
||||
// GpuLimiterThroughputBenchmarks so limiter acquisition does not allocate when we enter the
|
||||
// GPU scanning hot path.
|
||||
var sem = _semaphore;
|
||||
sem.Wait();
|
||||
return new Lease(sem);
|
||||
return new Releaser(sem);
|
||||
}
|
||||
|
||||
public static void SetLimit(int value)
|
||||
@@ -32,7 +32,7 @@ public static class GpuWorkLimiter
|
||||
|
||||
// Swap to a new semaphore for the new limit. We intentionally
|
||||
// do not dispose the old semaphore to avoid releasing handles
|
||||
// that may still be in use by existing Lease instances.
|
||||
// that may still be in use by existing Releaser instances.
|
||||
// TODO: Consolidate with GpuPrimeWorkLimiter so both limiters share a pooled SemaphoreSlim and avoid rebuilding the
|
||||
// limiter state whenever limits are adjusted from the CLI.
|
||||
_semaphore = new SemaphoreSlim(value, value);
|
||||
@@ -40,18 +40,25 @@ public static class GpuWorkLimiter
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Lease
|
||||
private sealed class Releaser : IDisposable
|
||||
{
|
||||
private readonly SemaphoreSlim _sem;
|
||||
private bool _disposed;
|
||||
|
||||
internal Lease(SemaphoreSlim sem)
|
||||
public Releaser(SemaphoreSlim sem)
|
||||
{
|
||||
_sem = sem;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_sem.Release();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public sealed class KernelContainer
|
||||
{
|
||||
// Serializes first-time initialization of kernels/buffers per accelerator.
|
||||
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>? Order;
|
||||
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>? Incremental;
|
||||
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>,
|
||||
ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>,
|
||||
ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2Mod;
|
||||
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? IncrementalOrder;
|
||||
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2ModOrder;
|
||||
|
||||
public Kernel? SmallPrimeFactor;
|
||||
|
||||
public Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? SpecialMax;
|
||||
|
||||
// Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length.
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallCycles;
|
||||
|
||||
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastOne;
|
||||
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastSeven;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastOne;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastSeven;
|
||||
|
||||
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimeFactorsPrimes;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorsSquares;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Order = null;
|
||||
// Incremental = null;
|
||||
// Pow2Mod = null;
|
||||
// IncrementalOrder = null;
|
||||
// Pow2ModOrder = null;
|
||||
// SmallPrimeFactor = null;
|
||||
// SpecialMax = null;
|
||||
|
||||
// SmallCycles?.Dispose();
|
||||
// SmallPrimesLastOne?.Dispose();
|
||||
// SmallPrimesLastSeven?.Dispose();
|
||||
// SmallPrimesPow2LastOne?.Dispose();
|
||||
// SmallPrimesPow2LastSeven?.Dispose();
|
||||
// SmallPrimeFactorsPrimes?.Dispose();
|
||||
// SmallPrimeFactorsSquares?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
[Flags]
|
||||
public enum KernelType
|
||||
{
|
||||
None,
|
||||
SmallCycles,
|
||||
SmallPrimes,
|
||||
Pow2ModKernelScan,
|
||||
OrderKernelScan,
|
||||
IncrementalKernelScan,
|
||||
IncrementalOrderKernelScan,
|
||||
Pow2ModOrderKernelScan
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
using ILGPU;
|
||||
using ILGPU.Algorithms;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
@@ -50,28 +53,15 @@ internal static class DivisorByDivisorKernels
|
||||
int endIndex = offset + count;
|
||||
if (hasCycle)
|
||||
{
|
||||
GpuUInt128 remainderValue = new(cycleRemainder);
|
||||
for (int exponentIndex = offset + 1; exponentIndex < endIndex; exponentIndex++)
|
||||
{
|
||||
ulong exponent = exponents[exponentIndex];
|
||||
ulong delta = exponent - previousExponent;
|
||||
previousExponent = exponent;
|
||||
|
||||
if (delta >= cycleLength)
|
||||
{
|
||||
delta -= cycleLength;
|
||||
if (delta >= cycleLength)
|
||||
{
|
||||
delta %= cycleLength;
|
||||
}
|
||||
}
|
||||
|
||||
ulong nextRemainder = cycleRemainder + delta;
|
||||
if (nextRemainder >= cycleLength)
|
||||
{
|
||||
nextRemainder -= cycleLength;
|
||||
}
|
||||
|
||||
cycleRemainder = nextRemainder;
|
||||
remainderValue.AddMod(delta, cycleLength);
|
||||
cycleRemainder = remainderValue.Low;
|
||||
|
||||
byte hit = 0;
|
||||
if (cycleRemainder == 0UL)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using ILGPU;
|
||||
using ILGPU.Algorithms;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using ILGPU;
|
||||
using ILGPU.Algorithms;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
@@ -303,7 +305,7 @@ internal static class LucasLehmerKernels
|
||||
states[idx] = s;
|
||||
}
|
||||
|
||||
public static void Kernel(Index1D _, ulong exponent, GpuUInt128 modulus, ArrayView<GpuUInt128> state)
|
||||
public static void Kernel(Index1D index, ulong exponent, GpuUInt128 modulus, ArrayView<GpuUInt128> state)
|
||||
{
|
||||
// Lucas–Lehmer iteration in the field GF(2^p-1).
|
||||
// For p < 128, use a Mersenne-specific squaring + reduction to avoid
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
@@ -33,7 +32,7 @@ internal static class NttBarrettKernels
|
||||
}
|
||||
|
||||
// Barrett-based squaring: v = v^2 mod n for 128-bit n
|
||||
public static void SquareBarrett128Kernel(Index1D index, AcceleratorStream stream, ArrayView<GpuUInt128> data, ulong modHigh, ulong modLow, ulong muHigh, ulong muLow)
|
||||
public static void SquareBarrett128Kernel(Index1D index, ArrayView<GpuUInt128> data, ulong modHigh, ulong modLow, ulong muHigh, ulong muLow)
|
||||
{
|
||||
var v = data[index];
|
||||
Mul128Full(v.High, v.Low, v.High, v.Low, out var z3, out var z2, out var z1, out var z0);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
@@ -44,7 +43,7 @@ internal static class NttMontgomeryKernels
|
||||
data[index] = new GpuUInt128(0UL, v);
|
||||
}
|
||||
|
||||
public static void SquareMont64Kernel(Index1D index, AcceleratorStream stream, ArrayView<GpuUInt128> data, ulong modulus, ulong nPrime)
|
||||
public static void SquareMont64Kernel(Index1D index, ArrayView<GpuUInt128> data, ulong modulus, ulong nPrime)
|
||||
{
|
||||
ulong aR = data[index].Low;
|
||||
ulong v = MontMul64(aR, aR, modulus, nPrime);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using ILGPU;
|
||||
using ILGPU.Algorithms;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
@@ -1,80 +1,26 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu.Kernels;
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
internal static class Pow2MontgomeryKernels
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static GpuUInt128 AdvancePolynomialGpu(GpuUInt128 x, ulong c, ulong modulus)
|
||||
{
|
||||
x.Pow2();
|
||||
x.AddMod(c, modulus);
|
||||
return x;
|
||||
}
|
||||
public static void Pow2MontgomeryKernelKeepMontgomery(
|
||||
Index1D index,
|
||||
ArrayView1D<ulong, Stride1D.Dense> exponents,
|
||||
MontgomeryDivisorData divisor,
|
||||
ArrayView1D<ulong, Stride1D.Dense> results)
|
||||
{
|
||||
results[index] = ULongExtensions.Pow2MontgomeryModWindowedGpuKeepMontgomery(divisor, exponents[index]);
|
||||
}
|
||||
|
||||
public static void Pow2MontgomeryKernelKeepMontgomery(
|
||||
Index1D index,
|
||||
ArrayView1D<ulong, Stride1D.Dense> exponents,
|
||||
MontgomeryDivisorData divisor,
|
||||
ArrayView1D<ulong, Stride1D.Dense> results)
|
||||
{
|
||||
results[0] = ULongExtensions.Pow2MontgomeryModWindowedGpuKeepMontgomery(divisor, exponents[0]);
|
||||
}
|
||||
|
||||
public static void Pow2MontgomeryKernelConvertToStandard(
|
||||
Index1D index,
|
||||
ArrayView1D<ulong, Stride1D.Dense> exponents,
|
||||
MontgomeryDivisorData divisor,
|
||||
ArrayView1D<ulong, Stride1D.Dense> results)
|
||||
{
|
||||
results[0] = ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisor, exponents[0]);
|
||||
}
|
||||
|
||||
|
||||
public static void TryPollardRhoKernel(ulong n, int limit, ArrayView1D<ulong, Stride1D.Dense> randomState, ArrayView1D<byte, Stride1D.Dense> factored, ArrayView1D<ulong, Stride1D.Dense> factor)
|
||||
{
|
||||
factored[0] = 0;
|
||||
if ((n & 1UL) == 0UL)
|
||||
{
|
||||
factor[0] = 2UL;
|
||||
factored[0] = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
int attempt = 0;
|
||||
GpuUInt128 nGpuUInt128 = (GpuUInt128)n;
|
||||
DeterministicRandomGpu random = new(randomState[0]);
|
||||
while (attempt < limit)
|
||||
{
|
||||
ulong c = (random.NextUInt64() % (n - 1UL)) + 1UL;
|
||||
GpuUInt128 x = (random.NextUInt64() % (n - 2UL)) + 2UL;
|
||||
GpuUInt128 y = x;
|
||||
GpuUInt128 d = GpuUInt128.One;
|
||||
|
||||
while (d.CompareTo(GpuUInt128.One) == 0)
|
||||
{
|
||||
x = AdvancePolynomialGpu(x, c, modulus: n);
|
||||
y = AdvancePolynomialGpu(y, c, n);
|
||||
y = AdvancePolynomialGpu(y, c, n);
|
||||
GpuUInt128 diff = x.CompareTo(y) > 0 ? x - y : y - x;
|
||||
d = GpuUInt128.BinaryGcd(diff, nGpuUInt128);
|
||||
}
|
||||
|
||||
if (d != n)
|
||||
{
|
||||
if (d.High == 0)
|
||||
{
|
||||
factor[0] = d.Low;
|
||||
factored[0] = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
attempt++;
|
||||
}
|
||||
|
||||
randomState[0] = random.State;
|
||||
}
|
||||
public static void Pow2MontgomeryKernelConvertToStandard(
|
||||
Index1D index,
|
||||
ArrayView1D<ulong, Stride1D.Dense> exponents,
|
||||
MontgomeryDivisorData divisor,
|
||||
ArrayView1D<ulong, Stride1D.Dense> results)
|
||||
{
|
||||
results[index] = ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisor, exponents[index]);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,79 +0,0 @@
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
internal static partial class PrimeOrderGpuHeuristics
|
||||
{
|
||||
internal static void EvaluateSpecialMaxCandidatesKernel(
|
||||
Index1D index,
|
||||
ulong phi,
|
||||
ArrayView1D<ulong, Stride1D.Dense> factors,
|
||||
int factorCount,
|
||||
MontgomeryDivisorData divisor,
|
||||
ArrayView1D<ulong, Stride1D.Dense> candidates,
|
||||
ArrayView1D<ulong, Stride1D.Dense> resultOut)
|
||||
{
|
||||
if (index != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int actual = 0;
|
||||
for (int i = 0; i < factorCount; i++)
|
||||
{
|
||||
ulong factor = factors[i];
|
||||
if (factor <= 1UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong reduced = phi / factor;
|
||||
if (reduced == 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
candidates[actual] = reduced;
|
||||
actual++;
|
||||
}
|
||||
|
||||
if (actual == 0)
|
||||
{
|
||||
resultOut[0] = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 1; i < actual; i++)
|
||||
{
|
||||
ulong current = candidates[i];
|
||||
int insert = i - 1;
|
||||
while (insert >= 0 && candidates[insert] > current)
|
||||
{
|
||||
candidates[insert + 1] = candidates[insert];
|
||||
insert--;
|
||||
}
|
||||
|
||||
candidates[insert + 1] = current;
|
||||
}
|
||||
|
||||
var divisorPartial = new GpuDivisorPartialData(divisor.Modulus);
|
||||
var stepper = new ExponentRemainderStepperGpu(divisorPartial);
|
||||
if (stepper.InitializeIsUnityGpu(candidates[0]))
|
||||
{
|
||||
Atomic.Exchange(ref resultOut[0], 0);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 1; i < actual; i++)
|
||||
{
|
||||
if (stepper.ComputeNextIsUnityGpu(candidates[i]))
|
||||
{
|
||||
Atomic.Exchange(ref resultOut[0], 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Atomic.Exchange(ref resultOut[0], 1);
|
||||
}
|
||||
}
|
||||
@@ -1,73 +1,33 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU;
|
||||
using ILGPU.Algorithms;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public enum HeuristicGpuDivisorTableKind : byte
|
||||
{
|
||||
GroupA = 0,
|
||||
GroupBEnding1 = 1,
|
||||
GroupBEnding7 = 7,
|
||||
GroupBEnding9 = 9,
|
||||
}
|
||||
|
||||
internal static class PrimeTesterKernels
|
||||
{
|
||||
// GPU kernel: small-prime sieve only. Returns 1 if passes sieve (probable prime), 0 otherwise.
|
||||
public static void SmallPrimeSieveKernel(
|
||||
Index1D index,
|
||||
ArrayView<ulong> numbers,
|
||||
ArrayView<uint> smallPrimesLastOne,
|
||||
ArrayView<uint> smallPrimesLastSeven,
|
||||
ArrayView<uint> smallPrimesLastThree,
|
||||
ArrayView<uint> smallPrimesLastNine,
|
||||
ArrayView<ulong> smallPrimesPow2LastOne,
|
||||
ArrayView<ulong> smallPrimesPow2LastSeven,
|
||||
ArrayView<ulong> smallPrimesPow2LastThree,
|
||||
ArrayView<ulong> smallPrimesPow2LastNine,
|
||||
ArrayView<byte> results)
|
||||
public static void SmallPrimeSieveKernel(Index1D index, ArrayView<ulong> numbers, ArrayView<uint> smallPrimes, ArrayView<byte> results)
|
||||
{
|
||||
ulong n = numbers[index];
|
||||
byte result = 1;
|
||||
|
||||
ArrayView<uint> primes;
|
||||
ArrayView<ulong> primeSquares;
|
||||
ulong lastDigit = n % 10UL;
|
||||
switch (lastDigit)
|
||||
{
|
||||
case 1UL:
|
||||
primes = smallPrimesLastOne;
|
||||
primeSquares = smallPrimesPow2LastOne;
|
||||
break;
|
||||
case 3UL:
|
||||
primes = smallPrimesLastThree;
|
||||
primeSquares = smallPrimesPow2LastThree;
|
||||
break;
|
||||
case 7UL:
|
||||
primes = smallPrimesLastSeven;
|
||||
primeSquares = smallPrimesPow2LastSeven;
|
||||
break;
|
||||
case 9UL:
|
||||
primes = smallPrimesLastNine;
|
||||
primeSquares = smallPrimesPow2LastNine;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
// EvenPerfectBitScanner filters candidates so production GPU launches only see:
|
||||
// - n >= 31 (the scanner starts at 136,279,841 and test mode seeds 31)
|
||||
// - n odd (candidate generators and ModResidueTracker eliminate even exponents)
|
||||
// - n not divisible by 5 (residue prefiltering and AddPrimes transform cover these composites)
|
||||
// Tests should honor the same constraints so the host wrapper can remain branchless.
|
||||
|
||||
int length = (int)primes.Length;
|
||||
long length = smallPrimes.Length;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
ulong primeSquare = primeSquares[i];
|
||||
ulong prime = smallPrimes[i];
|
||||
ulong primeSquare = (ulong)prime * prime;
|
||||
if (primeSquare > n)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ulong prime = primes[i];
|
||||
if (n % prime == 0UL)
|
||||
{
|
||||
result = 0;
|
||||
@@ -78,69 +38,16 @@ internal static class PrimeTesterKernels
|
||||
results[index] = result;
|
||||
}
|
||||
|
||||
public static void HeuristicTrialCombinedDivisionKernel(
|
||||
Index1D index,
|
||||
byte nMod10,
|
||||
ArrayView<int> resultFlag,
|
||||
ulong n,
|
||||
ulong maxDivisorSquare,
|
||||
HeuristicCombinedGpuViews tables)
|
||||
public static void HeuristicTrialDivisionKernel(Index1D index, ArrayView<ulong> divisors, ulong n, ArrayView<byte> results)
|
||||
{
|
||||
// byte nMod10 = (byte)(n % 10UL);
|
||||
ArrayView1D<ulong, Stride1D.Dense> divisors = tables.SelectDivisors(nMod10);
|
||||
ArrayView1D<ulong, Stride1D.Dense> divisorSquares = tables.SelectDivisorSquares(nMod10);
|
||||
|
||||
int divisorLength = (int)divisorSquares.Length;
|
||||
int threadIndex = index;
|
||||
if (threadIndex >= divisorLength)
|
||||
ulong divisor = divisors[index];
|
||||
if (divisor <= 1UL)
|
||||
{
|
||||
results[index] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
ulong divisorSquare = divisorSquares[threadIndex];
|
||||
if (divisorSquare > maxDivisorSquare)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ulong divisor = divisors[threadIndex];
|
||||
divisor = n % divisor;
|
||||
if (divisor == 0UL)
|
||||
{
|
||||
resultFlag[0] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static void HeuristicTrialGroupABDivisionKernel(
|
||||
Index1D index,
|
||||
ArrayView<int> resultFlag,
|
||||
ulong n,
|
||||
ulong maxDivisorSquare,
|
||||
HeuristicGpuDivisorTableKind tableKind,
|
||||
HeuristicGroupABGpuViews tables)
|
||||
{
|
||||
ArrayView1D<ulong, Stride1D.Dense> divisors = tables.SelectDivisors(tableKind);
|
||||
ArrayView1D<ulong, Stride1D.Dense> divisorSquares = tables.SelectDivisorSquares(tableKind);
|
||||
|
||||
int divisorLength = (int)divisorSquares.Length;
|
||||
int threadIndex = index;
|
||||
if (threadIndex >= divisorLength)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ulong divisorSquare = divisorSquares[threadIndex];
|
||||
if (divisorSquare > maxDivisorSquare)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ulong divisor = divisors[threadIndex];
|
||||
divisor = n % divisor;
|
||||
if (divisor == 0UL)
|
||||
{
|
||||
resultFlag[0] = 1;
|
||||
}
|
||||
results[index] = n % divisor == 0UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
public static void SharesFactorKernel(Index1D index, ArrayView<ulong> numbers, ArrayView<byte> results)
|
||||
@@ -153,37 +60,35 @@ internal static class PrimeTesterKernels
|
||||
|
||||
private static ulong BinaryGcdGpu(ulong u, ulong v)
|
||||
{
|
||||
// TODO: Replace this inline GPU binary GCD with the kernel extracted from
|
||||
// GpuUInt128BinaryGcdBenchmarks via GpuKernelPool so device callers reuse the
|
||||
// fully unrolled ladder instead of this branchy fallback.
|
||||
bool uIsZero = u == 0UL;
|
||||
bool vIsZero = v == 0UL;
|
||||
bool eitherZero = uIsZero | vIsZero;
|
||||
ulong zeroResult = uIsZero ? v : u;
|
||||
|
||||
ulong combined = (u | v) | 1UL;
|
||||
ulong combined = u | v | 1UL;
|
||||
int shift = XMath.TrailingZeroCount(combined);
|
||||
ulong currentU = u >> XMath.TrailingZeroCount(u | 1UL);
|
||||
ulong currentV = v >> XMath.TrailingZeroCount(v | 1UL);
|
||||
|
||||
while (true)
|
||||
ulong normalizedU = u >> XMath.TrailingZeroCount(u | 1UL);
|
||||
ulong normalizedV = v >> XMath.TrailingZeroCount(v | 1UL);
|
||||
|
||||
ulong currentU = normalizedU;
|
||||
ulong currentV = normalizedV;
|
||||
bool loopCondition = currentV != 0UL;
|
||||
|
||||
while (loopCondition)
|
||||
{
|
||||
currentV >>= XMath.TrailingZeroCount(currentV);
|
||||
bool swap = currentU > currentV;
|
||||
ulong minValue = ConditionalSelect(currentU, currentV, swap);
|
||||
ulong maxValue = ConditionalSelect(currentV, currentU, swap);
|
||||
currentV = maxValue - minValue;
|
||||
ulong minValue = XMath.Min(currentU, currentV);
|
||||
ulong maxValue = XMath.Max(currentU, currentV);
|
||||
currentU = minValue;
|
||||
if (currentV == 0UL)
|
||||
{
|
||||
ulong gcd = currentU << shift;
|
||||
return eitherZero ? zeroResult : gcd;
|
||||
}
|
||||
currentV = maxValue - minValue;
|
||||
loopCondition = currentV != 0UL;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong ConditionalSelect(ulong left, ulong right, bool useRight)
|
||||
{
|
||||
ulong mask = useRight ? ulong.MaxValue : 0UL;
|
||||
return left ^ ((left ^ right) & mask);
|
||||
ulong gcd = currentU << shift;
|
||||
return eitherZero ? zeroResult : gcd;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Concurrent;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
@@ -8,157 +9,127 @@ namespace PerfectNumbers.Core.Gpu;
|
||||
/// </summary>
|
||||
public sealed class MersenneNumberDivisorGpuTester
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static Dictionary<Accelerator, Queue<MemoryBuffer1D<byte, Stride1D.Dense>>>? _resultBuffers;
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>> _kernelCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, MemoryBuffer1D<byte, Stride1D.Dense>> _resultBuffers = new();
|
||||
|
||||
private static Queue<MemoryBuffer1D<byte, Stride1D.Dense>> GetQueue(Accelerator accelerator)
|
||||
{
|
||||
var pool = _resultBuffers ??= [];
|
||||
if (!pool.TryGetValue(accelerator, out var bufferQueue))
|
||||
{
|
||||
bufferQueue = [];
|
||||
pool[accelerator] = bufferQueue;
|
||||
}
|
||||
private Action<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>> GetKernel(Accelerator accelerator) =>
|
||||
_kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>(DivisorKernels.Kernel));
|
||||
|
||||
return bufferQueue;
|
||||
}
|
||||
public static void BuildDivisorCandidates()
|
||||
{
|
||||
ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||
(ulong divisor, uint cycle)[] list = new (ulong divisor, uint cycle)[snapshot.Length / 2];
|
||||
ulong cycle;
|
||||
int count = 0, i, snapshotLength = snapshot.Length;
|
||||
for (i = 3; i < snapshotLength; i += 2)
|
||||
{
|
||||
cycle = snapshot[i];
|
||||
if (cycle == 0U)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
private static Action<AcceleratorStream, Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>> GetDivisorKernel(Accelerator accelerator)
|
||||
{
|
||||
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>(DivisorKernels.Kernel);
|
||||
list[count++] = ((ulong)i, (uint)cycle);
|
||||
}
|
||||
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>>();
|
||||
}
|
||||
|
||||
public static void BuildDivisorCandidates()
|
||||
{
|
||||
ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||
(ulong divisor, uint cycle)[] list = new (ulong divisor, uint cycle)[snapshot.Length / 2];
|
||||
ulong cycle;
|
||||
int count = 0, i, snapshotLength = snapshot.Length;
|
||||
for (i = 3; i < snapshotLength; i += 2)
|
||||
{
|
||||
cycle = snapshot[i];
|
||||
if (cycle == 0U)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
list[count++] = ((ulong)i, (uint)cycle);
|
||||
}
|
||||
|
||||
_divisorCandidates = count == 0 ? [] : list[..count];
|
||||
}
|
||||
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
|
||||
public bool IsDivisible(ulong exponent, in ReadOnlyGpuUInt128 divisor)
|
||||
{
|
||||
// var accelerator = SharedGpuContext.CreateAccelerator();
|
||||
var acceleratorIndex = AcceleratorPool.Shared.Rent();
|
||||
var accelerator = _accelerators[acceleratorIndex];
|
||||
var stream = accelerator.CreateStream();
|
||||
Queue<MemoryBuffer1D<byte, Stride1D.Dense>> resultBuffers = GetQueue(accelerator);
|
||||
if (!resultBuffers.TryDequeue(out var resultBuffer))
|
||||
{
|
||||
// lock(accelerator)
|
||||
{
|
||||
resultBuffer = accelerator.Allocate1D<byte>(1);
|
||||
}
|
||||
}
|
||||
_divisorCandidates = count == 0 ? [] : list[..count];
|
||||
}
|
||||
|
||||
public bool IsDivisible(ulong exponent, in ReadOnlyGpuUInt128 divisor)
|
||||
{
|
||||
var gpu = GpuContextPool.RentPreferred(preferCpu: false);
|
||||
var accelerator = gpu.Accelerator;
|
||||
var kernel = GetKernel(accelerator);
|
||||
var resultBuffer = _resultBuffers.GetOrAdd(accelerator, acc => acc.Allocate1D<byte>(1));
|
||||
// There is no point in clearing this buffer. We always override item [0] and never use it beyond item [0]
|
||||
// resultBuffer.MemSetToZero(stream);
|
||||
var divisorKernel = GetDivisorKernel(accelerator);
|
||||
divisorKernel(stream, 1, exponent, divisor, resultBuffer.View);
|
||||
byte result = 0;
|
||||
resultBuffer.View.CopyToCPU(stream, ref result, 1);
|
||||
stream.Synchronize();
|
||||
bool divisible = result != 0;
|
||||
resultBuffers.Enqueue(resultBuffer);
|
||||
stream.Dispose();
|
||||
// AcceleratorPool.Shared.Return(accelerator);
|
||||
// accelerator.Dispose();
|
||||
return divisible;
|
||||
}
|
||||
// resultBuffer.MemSetToZero();
|
||||
kernel(1, exponent, divisor, resultBuffer.View);
|
||||
accelerator.Synchronize();
|
||||
Span<byte> result = stackalloc byte[1];
|
||||
resultBuffer.View.CopyToCPU(ref result[0], 1);
|
||||
bool divisible = result[0] != 0;
|
||||
result[0] = 0;
|
||||
resultBuffer.View.CopyFromCPU(ref result[0], 1);
|
||||
accelerator.Synchronize();
|
||||
gpu.Dispose();
|
||||
return divisible;
|
||||
}
|
||||
|
||||
private static (ulong divisor, uint cycle)[]? _divisorCandidates = [];
|
||||
private static (ulong divisor, uint cycle)[]? _divisorCandidates = Array.Empty<(ulong divisor, uint cycle)>();
|
||||
|
||||
public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted)
|
||||
{
|
||||
ReadOnlyGpuUInt128 readOnlyDivisor;
|
||||
public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted)
|
||||
{
|
||||
ReadOnlyGpuUInt128 readOnlyDivisor;
|
||||
|
||||
if (d != UInt128.Zero)
|
||||
{
|
||||
readOnlyDivisor = new ReadOnlyGpuUInt128(d);
|
||||
if (IsDivisible(p, in readOnlyDivisor))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
if (d != UInt128.Zero)
|
||||
{
|
||||
readOnlyDivisor = new ReadOnlyGpuUInt128(d);
|
||||
if (IsDivisible(p, in readOnlyDivisor))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
divisorsExhausted = false;
|
||||
return true;
|
||||
}
|
||||
divisorsExhausted = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_divisorCandidates is { Length: > 0 } candidates)
|
||||
{
|
||||
int candidateCount = candidates.Length;
|
||||
for (int index = 0; index < candidateCount; index++)
|
||||
{
|
||||
(ulong candidateDivisor, uint cycle) = candidates[index];
|
||||
if (p % cycle != 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (_divisorCandidates is { Length: > 0 } candidates)
|
||||
{
|
||||
int candidateCount = candidates.Length;
|
||||
for (int index = 0; index < candidateCount; index++)
|
||||
{
|
||||
(ulong candidateDivisor, uint cycle) = candidates[index];
|
||||
if (p % cycle != 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
readOnlyDivisor = new ReadOnlyGpuUInt128(candidateDivisor); // Reusing readOnlyDivisor for candidate divisors.
|
||||
if (IsDivisible(p, in readOnlyDivisor))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
readOnlyDivisor = new ReadOnlyGpuUInt128(candidateDivisor); // Reusing readOnlyDivisor for candidate divisors.
|
||||
if (IsDivisible(p, in readOnlyDivisor))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UInt128 kMul2;
|
||||
var divisorCycles = MersenneDivisorCycles.Shared;
|
||||
UInt128 maxK2 = UInt128.MaxValue / ((UInt128)p << 1);
|
||||
ulong limit = divisorCyclesSearchLimit;
|
||||
if ((UInt128)limit > maxK2)
|
||||
{
|
||||
limit = (ulong)maxK2;
|
||||
}
|
||||
UInt128 kMul2;
|
||||
var divisorCycles = MersenneDivisorCycles.Shared;
|
||||
UInt128 maxK2 = UInt128.MaxValue / ((UInt128)p << 1);
|
||||
ulong limit = divisorCyclesSearchLimit;
|
||||
if ((UInt128)limit > maxK2)
|
||||
{
|
||||
limit = (ulong)maxK2;
|
||||
}
|
||||
|
||||
for (ulong k2 = 1UL; k2 <= limit; k2++)
|
||||
{
|
||||
kMul2 = (UInt128)k2 << 1;
|
||||
UInt128 candidate = checked(kMul2 * p);
|
||||
d = checked(candidate + UInt128.One);
|
||||
if (p < 64UL && d == ((UInt128)1 << (int)p) - UInt128.One)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (ulong k2 = 1UL; k2 <= limit; k2++)
|
||||
{
|
||||
kMul2 = (UInt128)k2 << 1;
|
||||
UInt128 candidate = checked(kMul2 * p);
|
||||
d = checked(candidate + UInt128.One);
|
||||
if (p < 64UL && d == ((UInt128)1 << (int)p) - UInt128.One)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UInt128 cycle128 = MersenneDivisorCycles.GetCycle(d);
|
||||
if ((UInt128)p % cycle128 != UInt128.Zero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
UInt128 cycle128 = MersenneDivisorCycles.GetCycle(d);
|
||||
if ((UInt128)p % cycle128 != UInt128.Zero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
readOnlyDivisor = new ReadOnlyGpuUInt128(d); // Reusing readOnlyDivisor for generated divisors.
|
||||
if (IsDivisible(p, in readOnlyDivisor))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
readOnlyDivisor = new ReadOnlyGpuUInt128(d); // Reusing readOnlyDivisor for generated divisors.
|
||||
if (IsDivisible(p, in readOnlyDivisor))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
divisorsExhausted = (UInt128)divisorCyclesSearchLimit >= maxK2;
|
||||
return true;
|
||||
}
|
||||
divisorsExhausted = (UInt128)divisorCyclesSearchLimit >= maxK2;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,122 +12,123 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
|
||||
private readonly GpuKernelType _kernelType = kernelType;
|
||||
private readonly bool _useGpuOrder = useGpuOrder;
|
||||
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
|
||||
public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
|
||||
{
|
||||
throw new NotImplementedException($"GPU incremental scanning requires the device cycle heuristics implementation (kernel: {_kernelType}, GPU order: {_useGpuOrder}).");
|
||||
|
||||
// GpuPrimeWorkLimiter.Acquire();
|
||||
var acceleratorIndex = AcceleratorPool.Shared.Rent();
|
||||
var accelerator = _accelerators[acceleratorIndex];
|
||||
var stream = accelerator.CreateStream();
|
||||
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
|
||||
var execution = gpuLease.EnterExecutionScope();
|
||||
var accelerator = gpuLease.Accelerator;
|
||||
var stream = gpuLease.Stream;
|
||||
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy
|
||||
UInt128 kStart = 1UL;
|
||||
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
|
||||
byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters
|
||||
|
||||
KernelContainer kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.Pow2ModKernelScan | KernelType.IncrementalKernelScan);
|
||||
var pow2Kernel = kernels.Pow2Mod!;
|
||||
var incKernel = kernels.Incremental!;
|
||||
var pow2Kernel = gpuLease.Pow2ModKernel;
|
||||
var incKernel = gpuLease.IncrementalKernel;
|
||||
ulong step10 = (exponent.Mod10() << 1).Mod10();
|
||||
ulong step8 = ((exponent & 7UL) << 1) & 7UL;
|
||||
ulong step3 = ((exponent % 3UL) << 1) % 3UL;
|
||||
ulong step5 = ((exponent % 5UL) << 1) % 5UL;
|
||||
GpuUInt128 twoPGpu = (GpuUInt128)twoP;
|
||||
var smallCyclesView = GpuKernelPool.GetSmallCyclesOnDevice(kernels);
|
||||
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||
ResiduePrimeViews primeViews = default;
|
||||
if (_kernelType == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
primeViews = GpuKernelPool.GetSmallPrimesOnDevice(kernels);
|
||||
primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(accelerator);
|
||||
}
|
||||
|
||||
var orderBuffer = accelerator.Allocate1D<ulong>(batchSize);
|
||||
// Avoid giant stack allocations that can trigger StackOverflow when batchSize is large.
|
||||
// Rent a reusable array from the shared pool instead.
|
||||
ArrayPool<ulong> ulongPool = ThreadStaticPools.UlongPool;
|
||||
ulong[] orderArray = ulongPool.Rent(batchSize);
|
||||
// Avoid giant stack allocations that can trigger StackOverflow when batchSize is large.
|
||||
// Rent a reusable array from the shared pool instead.
|
||||
ulong[] orderArray = ArrayPool<ulong>.Shared.Rent(batchSize);
|
||||
UInt128 remaining;
|
||||
int currentSize;
|
||||
int i;
|
||||
UInt128 q = UInt128.Zero;
|
||||
while (kStart <= maxK && Volatile.Read(ref isPrime))
|
||||
try
|
||||
{
|
||||
remaining = maxK - kStart + UInt128.One;
|
||||
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
|
||||
Span<ulong> orders = orderArray.AsSpan(0, currentSize);
|
||||
// Precompute residue automaton bases for this batch
|
||||
UInt128 q0 = twoP * kStart + UInt128.One;
|
||||
if (_kernelType == GpuKernelType.Pow2Mod)
|
||||
while (kStart <= maxK && Volatile.Read(ref isPrime))
|
||||
{
|
||||
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
|
||||
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
|
||||
pow2Kernel(
|
||||
stream,
|
||||
currentSize,
|
||||
exponent,
|
||||
twoPGpu,
|
||||
(GpuUInt128)kStart,
|
||||
last,
|
||||
divMul,
|
||||
kernelArgs,
|
||||
orderBuffer.View,
|
||||
smallCyclesView,
|
||||
primeViews.LastOne,
|
||||
primeViews.LastSeven,
|
||||
primeViews.LastOnePow2,
|
||||
primeViews.LastSevenPow2);
|
||||
}
|
||||
else
|
||||
{
|
||||
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
|
||||
incKernel(
|
||||
stream,
|
||||
currentSize,
|
||||
exponent,
|
||||
twoPGpu,
|
||||
(GpuUInt128)kStart,
|
||||
last,
|
||||
divMul,
|
||||
q0m10,
|
||||
q0m8,
|
||||
q0m3,
|
||||
q0m5,
|
||||
orderBuffer.View,
|
||||
smallCyclesView);
|
||||
}
|
||||
|
||||
orderBuffer.View.CopyToCPU(stream, ref MemoryMarshal.GetReference(orders), currentSize);
|
||||
stream.Synchronize();
|
||||
if (_kernelType == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
for (i = 0; i < currentSize; i++)
|
||||
remaining = maxK - kStart + UInt128.One;
|
||||
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
|
||||
Span<ulong> orders = orderArray.AsSpan(0, currentSize);
|
||||
// Precompute residue automaton bases for this batch
|
||||
UInt128 q0 = twoP * kStart + UInt128.One;
|
||||
if (_kernelType == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
if (orders[i] != 0UL)
|
||||
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
|
||||
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
|
||||
pow2Kernel(
|
||||
stream,
|
||||
currentSize,
|
||||
exponent,
|
||||
twoPGpu,
|
||||
(GpuUInt128)kStart,
|
||||
last,
|
||||
divMul,
|
||||
kernelArgs,
|
||||
orderBuffer.View,
|
||||
smallCyclesView,
|
||||
primeViews.LastOne,
|
||||
primeViews.LastSeven,
|
||||
primeViews.LastOnePow2,
|
||||
primeViews.LastSevenPow2);
|
||||
}
|
||||
else
|
||||
{
|
||||
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
|
||||
incKernel(
|
||||
stream,
|
||||
currentSize,
|
||||
exponent,
|
||||
twoPGpu,
|
||||
(GpuUInt128)kStart,
|
||||
last,
|
||||
divMul,
|
||||
q0m10,
|
||||
q0m8,
|
||||
q0m3,
|
||||
q0m5,
|
||||
orderBuffer.View,
|
||||
smallCyclesView);
|
||||
}
|
||||
|
||||
stream.Synchronize();
|
||||
orderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(orders), currentSize);
|
||||
if (_kernelType == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
for (i = 0; i < currentSize; i++)
|
||||
{
|
||||
Volatile.Write(ref isPrime, false);
|
||||
break;
|
||||
if (orders[i] != 0UL)
|
||||
{
|
||||
Volatile.Write(ref isPrime, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
q = twoP * kStart + UInt128.One;
|
||||
for (i = 0; i < currentSize && Volatile.Read(ref isPrime); i++, q += twoP)
|
||||
else
|
||||
{
|
||||
if (orders[i] != 0UL && q.IsPrimeCandidate())
|
||||
q = twoP * kStart + UInt128.One;
|
||||
for (i = 0; i < currentSize && Volatile.Read(ref isPrime); i++, q += twoP)
|
||||
{
|
||||
Volatile.Write(ref isPrime, false);
|
||||
if (orders[i] != 0UL && q.IsPrimeCandidate())
|
||||
{
|
||||
Volatile.Write(ref isPrime, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kStart += (UInt128)currentSize;
|
||||
kStart += (UInt128)currentSize;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<ulong>.Shared.Return(orderArray);
|
||||
orderBuffer.Dispose();
|
||||
execution.Dispose();
|
||||
gpuLease.Dispose();
|
||||
}
|
||||
|
||||
stream.Dispose();
|
||||
ulongPool.Return(orderArray);
|
||||
orderBuffer.Dispose();
|
||||
// GpuPrimeWorkLimiter.Release();
|
||||
}
|
||||
}
|
||||
@@ -8,129 +8,46 @@ namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public class MersenneNumberLucasLehmerGpuTester
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>>? _lucasLehmerKernel;
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>> KernelCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ArrayView<GpuUInt128>>> AddSmallKernelCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ArrayView<GpuUInt128>>> SubSmallKernelCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ArrayView<GpuUInt128>>> ReduceKernelCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>> IsZeroKernelCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>>> BatchKernelCache = new();
|
||||
|
||||
[ThreadStatic]
|
||||
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>? _addSmallKernel;
|
||||
private Action<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>> GetKernel(Accelerator accelerator)
|
||||
{
|
||||
return KernelCache.GetOrAdd(accelerator, accel => accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>(LucasLehmerKernels.Kernel));
|
||||
}
|
||||
|
||||
[ThreadStatic]
|
||||
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>? _subSmallKernel;
|
||||
|
||||
[ThreadStatic]
|
||||
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>? _reduceKernel;
|
||||
private Action<Index1D, ulong, ArrayView<GpuUInt128>> GetAddSmallKernel(Accelerator accelerator) =>
|
||||
AddSmallKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.AddSmallKernel));
|
||||
|
||||
[ThreadStatic]
|
||||
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>>? _isZeroKernel;
|
||||
private Action<Index1D, ulong, ArrayView<GpuUInt128>> GetSubSmallKernel(Accelerator accelerator) =>
|
||||
SubSmallKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.SubtractSmallKernel));
|
||||
|
||||
[ThreadStatic]
|
||||
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>>>? _lucasLehmerBatchKernel;
|
||||
private Action<Index1D, ulong, ArrayView<GpuUInt128>> GetReduceKernel(Accelerator accelerator) =>
|
||||
ReduceKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.ReduceModMersenneKernel));
|
||||
|
||||
private static Action<AcceleratorStream, Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>> GetLucasLehmerKernel(Accelerator accelerator)
|
||||
{
|
||||
var pool = _lucasLehmerKernel ??= [];
|
||||
if (pool.TryGetValue(accelerator, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
private Action<Index1D, ArrayView<GpuUInt128>, ArrayView<byte>> GetIsZeroKernel(Accelerator accelerator) =>
|
||||
IsZeroKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>(LucasLehmerKernels.IsZeroKernel));
|
||||
|
||||
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>(LucasLehmerKernels.Kernel);
|
||||
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
|
||||
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>>();
|
||||
pool[accelerator] = cached;
|
||||
return cached;
|
||||
}
|
||||
|
||||
private static Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>> GetAddSmallKernel(Accelerator accelerator)
|
||||
{
|
||||
var pool = _addSmallKernel ??= [];
|
||||
if (pool.TryGetValue(accelerator, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.AddSmallKernel);
|
||||
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
|
||||
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>();
|
||||
pool[accelerator] = cached;
|
||||
return cached;
|
||||
}
|
||||
|
||||
private static Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>> GetSubSmallKernel(Accelerator accelerator)
|
||||
{
|
||||
var pool = _subSmallKernel ??= [];
|
||||
if (pool.TryGetValue(accelerator, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.SubtractSmallKernel);
|
||||
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
|
||||
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>();
|
||||
pool[accelerator] = cached;
|
||||
return cached;
|
||||
}
|
||||
|
||||
private static Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>> GetReduceKernel(Accelerator accelerator)
|
||||
{
|
||||
var pool = _reduceKernel ??= [];
|
||||
if (pool.TryGetValue(accelerator, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.ReduceModMersenneKernel);
|
||||
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
|
||||
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>();
|
||||
pool[accelerator] = cached;
|
||||
return cached;
|
||||
}
|
||||
|
||||
private static Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<byte>> GetIsZeroKernel(Accelerator accelerator)
|
||||
{
|
||||
var pool = _isZeroKernel ??= [];
|
||||
if (pool.TryGetValue(accelerator, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>(LucasLehmerKernels.IsZeroKernel);
|
||||
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
|
||||
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>>();
|
||||
pool[accelerator] = cached;
|
||||
return cached;
|
||||
}
|
||||
|
||||
private static Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>> GetLucasLehmerBatchKernel(Accelerator accelerator)
|
||||
{
|
||||
var pool = _lucasLehmerBatchKernel ??= [];
|
||||
if (pool.TryGetValue(accelerator, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>>(LucasLehmerKernels.KernelBatch);
|
||||
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
|
||||
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>>>();
|
||||
pool[accelerator] = cached;
|
||||
return cached;
|
||||
}
|
||||
private Action<Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>> GetBatchKernel(Accelerator accelerator) =>
|
||||
BatchKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>>(LucasLehmerKernels.KernelBatch));
|
||||
|
||||
// Configurable LL slice size to keep kernels short. Default 32.
|
||||
public int SliceSize = 32;
|
||||
|
||||
public bool IsMersennePrime(ulong exponent)
|
||||
{
|
||||
// Default to global kernel preference for backward compatibility.
|
||||
bool runOnGpu = !GpuContextPool.ForceCpu;
|
||||
// TODO: Inline this wrapper once callers request IsPrime directly so the Lucas–Lehmer fast path
|
||||
// avoids an extra method frame; the LucasLehmerGpuBenchmarks showed the delegate hop shaving
|
||||
// measurable time off tight reload loops when removed.
|
||||
return IsPrime(exponent, runOnGpu);
|
||||
}
|
||||
|
||||
public bool IsPrime(ulong exponent, bool runOnGpu)
|
||||
{
|
||||
// Early rejections aligned with incremental/order sieves, but safe for small p:
|
||||
@@ -147,11 +64,13 @@ public class MersenneNumberLucasLehmerGpuTester
|
||||
return false;
|
||||
}
|
||||
|
||||
var limiter = GpuPrimeWorkLimiter.Acquire();
|
||||
bool result;
|
||||
if (exponent >= 128UL)
|
||||
{
|
||||
if (!TryGetNttParameters(exponent, out var nttMod, out var primitiveRoot))
|
||||
{
|
||||
limiter.Dispose();
|
||||
throw new NotSupportedException("NTT parameters for the given exponent are not available.");
|
||||
}
|
||||
|
||||
@@ -159,22 +78,19 @@ public class MersenneNumberLucasLehmerGpuTester
|
||||
}
|
||||
else
|
||||
{
|
||||
// GpuPrimeWorkLimiter.Acquire();
|
||||
var acceleratorIndex = AcceleratorPool.Shared.Rent();
|
||||
var accelerator = _accelerators[acceleratorIndex];
|
||||
var stream = accelerator.CreateStream();
|
||||
var gpu = GpuContextPool.RentPreferred(preferCpu: !runOnGpu);
|
||||
var accelerator = gpu.Accelerator;
|
||||
var kernel = GetKernel(accelerator);
|
||||
var modulus = new GpuUInt128(((UInt128)1 << (int)exponent) - 1UL); // TODO: Cache these Mersenne moduli per exponent so LL GPU runs skip rebuilding them every launch.
|
||||
var buffer = accelerator.Allocate1D<GpuUInt128>(1);
|
||||
var lucasLehmerKernel = GetLucasLehmerKernel(accelerator);
|
||||
lucasLehmerKernel(stream, 1, exponent, modulus, buffer.View);
|
||||
stream.Synchronize();
|
||||
// TODO: Replace this with .CopyToCPU with pre-allocated buffer using stream. Synchronize on the stream after copy.
|
||||
var buffer = accelerator.Allocate1D<GpuUInt128>(1);
|
||||
kernel(1, exponent, modulus, buffer.View);
|
||||
accelerator.Synchronize();
|
||||
result = buffer.GetAsArray1D()[0].IsZero;
|
||||
buffer.Dispose();
|
||||
stream.Dispose();
|
||||
gpu.Dispose();
|
||||
}
|
||||
|
||||
// GpuPrimeWorkLimiter.Release();
|
||||
limiter.Dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -194,18 +110,16 @@ public class MersenneNumberLucasLehmerGpuTester
|
||||
}
|
||||
}
|
||||
|
||||
var acceleratorIndex = AcceleratorPool.Shared.Rent();
|
||||
var accelerator = _accelerators[acceleratorIndex];
|
||||
var stream = accelerator.CreateStream();
|
||||
var gpu = GpuContextPool.RentPreferred(preferCpu: false);
|
||||
var accelerator = gpu.Accelerator;
|
||||
var kernel = GetBatchKernel(accelerator);
|
||||
|
||||
var expBuffer = accelerator.Allocate1D<ulong>(count);
|
||||
ArrayPool<ulong> ulongPool = ThreadStaticPools.UlongPool;
|
||||
ulong[] expArray = ulongPool.Rent(count);
|
||||
ulong[] expArray = ArrayPool<ulong>.Shared.Rent(count);
|
||||
exponents.CopyTo(expArray);
|
||||
expBuffer.View.CopyFromCPU(stream, ref expArray[0], count);
|
||||
expBuffer.View.CopyFromCPU(ref expArray[0], count);
|
||||
|
||||
ArrayPool<GpuUInt128> gpuUInt128Pool = ThreadStaticPools.GpuUInt128Pool;
|
||||
GpuUInt128[] modulusArray = gpuUInt128Pool.Rent(count);
|
||||
GpuUInt128[] modulusArray = ArrayPool<GpuUInt128>.Shared.Rent(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
// TODO: Replace this per-exponent shift with a small shared table (one entry per supported exponent < 128)
|
||||
@@ -214,20 +128,19 @@ public class MersenneNumberLucasLehmerGpuTester
|
||||
}
|
||||
|
||||
var modBuffer = accelerator.Allocate1D<GpuUInt128>(count);
|
||||
modBuffer.View.CopyFromCPU(stream, ref modulusArray[0], count);
|
||||
modBuffer.View.CopyFromCPU(ref modulusArray[0], count);
|
||||
|
||||
var stateBuffer = accelerator.Allocate1D<GpuUInt128>(count);
|
||||
var lucasLehmerBatchKernel = GetLucasLehmerBatchKernel(accelerator);
|
||||
lucasLehmerBatchKernel(stream, count, expBuffer.View, modBuffer.View, stateBuffer.View);
|
||||
stateBuffer.View.CopyToCPU(stream, ref residues[0], count);
|
||||
stream.Synchronize();
|
||||
var stateBuffer = accelerator.Allocate1D<GpuUInt128>(count);
|
||||
kernel(count, expBuffer.View, modBuffer.View, stateBuffer.View);
|
||||
accelerator.Synchronize();
|
||||
stateBuffer.View.CopyToCPU(ref residues[0], count);
|
||||
|
||||
stateBuffer.Dispose();
|
||||
modBuffer.Dispose();
|
||||
expBuffer.Dispose();
|
||||
gpuUInt128Pool.Return(modulusArray);
|
||||
ulongPool.Return(expArray);
|
||||
stream.Dispose();
|
||||
ArrayPool<GpuUInt128>.Shared.Return(modulusArray);
|
||||
ArrayPool<ulong>.Shared.Return(expArray);
|
||||
gpu.Dispose();
|
||||
}
|
||||
|
||||
public void IsMersennePrimeBatch(ReadOnlySpan<ulong> exponents, Span<bool> results)
|
||||
@@ -238,27 +151,29 @@ public class MersenneNumberLucasLehmerGpuTester
|
||||
throw new ArgumentException("Result span too small", nameof(results));
|
||||
}
|
||||
|
||||
ArrayPool<GpuUInt128> gpuUInt128Pool = ThreadStaticPools.GpuUInt128Pool;
|
||||
GpuUInt128[] buffer = gpuUInt128Pool.Rent(count);
|
||||
ComputeResidues(exponents, buffer.AsSpan(0, count));
|
||||
for (int i = 0; i < count; i++)
|
||||
GpuUInt128[] buffer = ArrayPool<GpuUInt128>.Shared.Rent(count);
|
||||
try
|
||||
{
|
||||
results[i] = buffer[i].IsZero;
|
||||
ComputeResidues(exponents, buffer.AsSpan(0, count));
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
results[i] = buffer[i].IsZero;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<GpuUInt128>.Shared.Return(buffer);
|
||||
}
|
||||
gpuUInt128Pool.Return(buffer);
|
||||
}
|
||||
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
|
||||
private bool IsMersennePrimeNtt(ulong exponent, GpuUInt128 nttMod, GpuUInt128 primitiveRoot, bool runOnGpu)
|
||||
{
|
||||
var acceleratorIndex = AcceleratorPool.Shared.Rent();
|
||||
var accelerator = _accelerators[acceleratorIndex];
|
||||
var stream = accelerator.CreateStream();
|
||||
|
||||
var gpu = GpuContextPool.RentPreferred(preferCpu: !runOnGpu);
|
||||
var accelerator = gpu.Accelerator;
|
||||
var addKernel = GetAddSmallKernel(accelerator);
|
||||
var subKernel = GetSubSmallKernel(accelerator);
|
||||
var reduceKernel = GetReduceKernel(accelerator);
|
||||
var isZeroKernel = GetIsZeroKernel(accelerator);
|
||||
var reduceKernel = GetReduceKernel(accelerator);
|
||||
var zeroKernel = GetIsZeroKernel(accelerator);
|
||||
|
||||
int limbCount = (int)((exponent + 127UL) / 128UL);
|
||||
int target = limbCount * 2;
|
||||
@@ -268,9 +183,8 @@ public class MersenneNumberLucasLehmerGpuTester
|
||||
transformLength <<= 1;
|
||||
}
|
||||
var stateBuffer = accelerator.Allocate1D<GpuUInt128>(transformLength);
|
||||
stateBuffer.MemSetToZero(stream);
|
||||
var addSmallKernel = GetAddSmallKernel(accelerator);
|
||||
addSmallKernel(stream, 1, 4UL, stateBuffer.View);
|
||||
stateBuffer.MemSetToZero();
|
||||
addKernel(1, 4UL, stateBuffer.View);
|
||||
|
||||
// TODO(LL-SLICE): Slice Lucas–Lehmer iterations into short batches to
|
||||
// avoid long-running kernels and TDR. Example: process 8–64 iterations
|
||||
@@ -285,26 +199,27 @@ public class MersenneNumberLucasLehmerGpuTester
|
||||
for (int s = 0; s < iter; s++)
|
||||
{
|
||||
// Square in NTT domain with staged kernels (short runtime per kernel).
|
||||
NttGpuMath.SquareDevice(accelerator, stream, stateBuffer.View, nttMod, primitiveRoot);
|
||||
subKernel(stream, 1, 2UL, stateBuffer.View);
|
||||
reduceKernel(stream, 1, exponent, stateBuffer.View);
|
||||
NttGpuMath.SquareDevice(accelerator, stateBuffer.View, nttMod, primitiveRoot);
|
||||
subKernel(1, 2UL, stateBuffer.View);
|
||||
reduceKernel(1, exponent, stateBuffer.View);
|
||||
}
|
||||
|
||||
// Synchronize between slices to yield to scheduler and avoid TDR.
|
||||
accelerator.Synchronize();
|
||||
i += (ulong)iter;
|
||||
}
|
||||
|
||||
reduceKernel(stream, 1, exponent, stateBuffer.View);
|
||||
var resultBuffer = accelerator.Allocate1D<byte>(1);
|
||||
isZeroKernel(stream, 1, stateBuffer.View, resultBuffer.View);
|
||||
Span<byte> result = stackalloc byte[1];
|
||||
resultBuffer.View.CopyToCPU(stream, result);
|
||||
stream.Synchronize();
|
||||
|
||||
reduceKernel(1, exponent, stateBuffer.View);
|
||||
|
||||
var resultBuffer = accelerator.Allocate1D<byte>(1);
|
||||
zeroKernel(1, stateBuffer.View, resultBuffer.View);
|
||||
accelerator.Synchronize();
|
||||
byte[] result = new byte[1];
|
||||
resultBuffer.View.CopyToCPU(result);
|
||||
bool isPrime = result[0] != 0;
|
||||
resultBuffer.Dispose();
|
||||
stateBuffer.Dispose();
|
||||
stream.Dispose();
|
||||
gpu.Dispose();
|
||||
return isPrime;
|
||||
}
|
||||
|
||||
@@ -573,4 +488,6 @@ public class MersenneNumberLucasLehmerGpuTester
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -7,73 +7,67 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO
|
||||
private readonly GpuKernelType _kernelType = kernelType;
|
||||
private readonly bool _useGpuOrder = useGpuOrder;
|
||||
|
||||
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
|
||||
|
||||
public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
|
||||
{
|
||||
// GpuPrimeWorkLimiter.Acquire();
|
||||
// var accelerator = SharedGpuContext.Accelerator;
|
||||
var acceleratorIndex = AcceleratorPool.Shared.Rent();
|
||||
var accelerator = _accelerators[acceleratorIndex];
|
||||
var stream = accelerator.CreateStream();
|
||||
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
|
||||
var execution = gpuLease.EnterExecutionScope();
|
||||
var accelerator = gpuLease.Accelerator;
|
||||
var stream = gpuLease.Stream;
|
||||
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR
|
||||
UInt128 kStart = UInt128.One;
|
||||
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
|
||||
byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters
|
||||
|
||||
KernelContainer kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.Pow2ModOrderKernelScan | KernelType.IncrementalOrderKernelScan);
|
||||
var kernel = _kernelType switch
|
||||
{
|
||||
GpuKernelType.Pow2Mod => kernels.Pow2ModOrder!,
|
||||
_ => kernels.IncrementalOrder!,
|
||||
};
|
||||
var kernel = _kernelType switch
|
||||
{
|
||||
GpuKernelType.Pow2Mod => gpuLease.Pow2ModOrderKernel,
|
||||
_ => gpuLease.IncrementalOrderKernel,
|
||||
};
|
||||
|
||||
MemoryBuffer1D<int, ILGPU.Stride1D.Dense>? foundBuffer;
|
||||
// lock (accelerator)
|
||||
{
|
||||
foundBuffer = accelerator.Allocate1D<int>(1); // TODO: Replace this allocation with the pooled device buffer from GpuOrderKernelBenchmarks so repeated scans reuse the pinned staging memory instead of allocating per run.
|
||||
|
||||
}
|
||||
exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
|
||||
|
||||
UInt128 remaining;
|
||||
int currentSize;
|
||||
int found = 0;
|
||||
|
||||
var smallCyclesView = GpuKernelPool.GetSmallCyclesOnDevice(kernels);
|
||||
while (kStart <= maxK && Volatile.Read(ref isPrime))
|
||||
{
|
||||
remaining = maxK - kStart + 1UL;
|
||||
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
|
||||
// This clear is required because the result may not always be set. Remove it after the kernels are modified to always set the result.
|
||||
foundBuffer.MemSetToZero(stream);
|
||||
// Precompute residue automaton bases for this batch
|
||||
UInt128 q0 = twoP * kStart + 1UL;
|
||||
// TODO: Replace these modulo recomputations with the divisor-cycle stepping deltas captured in
|
||||
// MersenneDivisorCycleLengthGpuBenchmarks so each batch advances via cached residues from the
|
||||
// single snapshot block instead of recalculating mod 10/8/5/3 in every iteration.
|
||||
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
|
||||
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
|
||||
|
||||
// Ensure device has small cycles table for early rejections in order kernels
|
||||
kernel(
|
||||
stream, currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart,
|
||||
last, divMul, kernelArgs, foundBuffer.View, smallCyclesView
|
||||
);
|
||||
|
||||
foundBuffer.View.CopyToCPU(stream, ref found, 1);
|
||||
stream.Synchronize();
|
||||
if (found != 0)
|
||||
var foundBuffer = accelerator.Allocate1D<int>(1); // TODO: Replace this allocation with the pooled device buffer from GpuOrderKernelBenchmarks so repeated scans reuse the pinned staging memory instead of allocating per run.
|
||||
exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
|
||||
try
|
||||
{
|
||||
UInt128 remaining;
|
||||
int currentSize;
|
||||
int found = 0;
|
||||
while (kStart <= maxK && Volatile.Read(ref isPrime))
|
||||
{
|
||||
Volatile.Write(ref isPrime, false);
|
||||
break;
|
||||
}
|
||||
remaining = maxK - kStart + 1UL;
|
||||
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
|
||||
// This clear is required because the result may not always be set. Remove it after the kernels are modified to always set the result.
|
||||
foundBuffer.MemSetToZero();
|
||||
// Precompute residue automaton bases for this batch
|
||||
UInt128 q0 = twoP * kStart + 1UL;
|
||||
// TODO: Replace these modulo recomputations with the divisor-cycle stepping deltas captured in
|
||||
// MersenneDivisorCycleLengthGpuBenchmarks so each batch advances via cached residues from the
|
||||
// single snapshot block instead of recalculating mod 10/8/5/3 in every iteration.
|
||||
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
|
||||
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
|
||||
|
||||
kStart += (UInt128)currentSize;
|
||||
|
||||
// Ensure device has small cycles table for early rejections in order kernels
|
||||
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||
kernel(stream, currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, divMul,
|
||||
kernelArgs, foundBuffer.View, smallCyclesView);
|
||||
|
||||
stream.Synchronize();
|
||||
foundBuffer.View.CopyToCPU(ref found, 1);
|
||||
if (found != 0)
|
||||
{
|
||||
Volatile.Write(ref isPrime, false);
|
||||
break;
|
||||
}
|
||||
|
||||
kStart += (UInt128)currentSize;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
foundBuffer.Dispose();
|
||||
execution.Dispose();
|
||||
gpuLease.Dispose();
|
||||
}
|
||||
|
||||
stream.Dispose();
|
||||
foundBuffer.Dispose();
|
||||
// GpuPrimeWorkLimiter.Release();
|
||||
}
|
||||
}
|
||||
Loaded 100 of 135 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user