Compare commits

..
Author SHA1 Message Date
Marek Lemańczyk 0466d98434 Hoist by-divisor invariants and split mod-8 updates 2025-10-27 16:48:02 +01:00
135 changed files with 3828 additions and 6660 deletions

No files matched your search

+1 -3
View File
@@ -9,7 +9,5 @@
"Mersenne", "Mersenne",
"primality", "primality",
"suff" "suff"
], ]
"geminicodeassist.project": "evenperfectbitscanner",
"todo-tree.tree.showCountsInTree": true
} }
@@ -1,3 +1,4 @@
using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -32,7 +32,7 @@ public class GpuUInt128Montgomery64Benchmarks
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public ulong ExtensionMontgomeryMultiply() public ulong ExtensionMontgomeryMultiply()
{ {
return Input.Left.MontgomeryMultiplyCpu(Input.Right, Input.Modulus, Input.NPrime); return Input.Left.MontgomeryMultiply(Input.Right, Input.Modulus, Input.NPrime);
} }
/// <summary> /// <summary>
@@ -1,5 +1,6 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using PerfectNumbers.Core;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
namespace EvenPerfectBitScanner.Benchmarks; namespace EvenPerfectBitScanner.Benchmarks;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using PerfectNumbers.Core; using PerfectNumbers.Core;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,3 +1,5 @@
using System;
namespace EvenPerfectBitScanner.Benchmarks; namespace EvenPerfectBitScanner.Benchmarks;
public enum ModNumericType public enum ModNumericType
@@ -1,3 +1,4 @@
using System;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,8 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
namespace EvenPerfectBitScanner.Benchmarks; namespace EvenPerfectBitScanner.Benchmarks;
@@ -1,3 +1,4 @@
using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -100,7 +101,7 @@ public class Pow2ModBenchmarks
for (int i = 0; i < SampleCount; i++) for (int i = 0; i < SampleCount; i++)
{ {
checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertGpu(divisors[i]); checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
} }
return checksum; return checksum;
@@ -639,7 +640,7 @@ public class Pow2ModBenchmarks
ulong nPrime = ComputeMontgomeryNPrime(modulus); ulong nPrime = ComputeMontgomeryNPrime(modulus);
ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus); ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus);
ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, 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( return new MontgomeryDivisorData(
modulus, modulus,
@@ -735,22 +736,22 @@ public class Pow2ModBenchmarks
if (exponent == 0UL) if (exponent == 0UL)
{ {
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); return result.MontgomeryMultiply(1UL, modulus, nPrime);
} }
int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent); int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent);
for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--) for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--)
{ {
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime); result = result.MontgomeryMultiply(result, modulus, nPrime);
if (((exponent >> bitIndex) & 1UL) != 0UL) 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) private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor)
@@ -771,35 +772,35 @@ public class Pow2ModBenchmarks
ulong nibble = remainingExponent & 0xFUL; ulong nibble = remainingExponent & 0xFUL;
ulong base0 = baseVal; ulong base0 = baseVal;
ulong base1 = base0.MontgomeryMultiplyCpu(base0, modulus, nPrime); ulong base1 = base0.MontgomeryMultiply(base0, modulus, nPrime);
ulong base2 = base1.MontgomeryMultiplyCpu(base1, modulus, nPrime); ulong base2 = base1.MontgomeryMultiply(base1, modulus, nPrime);
ulong base3 = base2.MontgomeryMultiplyCpu(base2, modulus, nPrime); ulong base3 = base2.MontgomeryMultiply(base2, modulus, nPrime);
if ((nibble & 1UL) != 0UL) if ((nibble & 1UL) != 0UL)
{ {
result = result.MontgomeryMultiplyCpu(base0, modulus, nPrime); result = result.MontgomeryMultiply(base0, modulus, nPrime);
} }
if ((nibble & 2UL) != 0UL) if ((nibble & 2UL) != 0UL)
{ {
result = result.MontgomeryMultiplyCpu(base1, modulus, nPrime); result = result.MontgomeryMultiply(base1, modulus, nPrime);
} }
if ((nibble & 4UL) != 0UL) if ((nibble & 4UL) != 0UL)
{ {
result = result.MontgomeryMultiplyCpu(base2, modulus, nPrime); result = result.MontgomeryMultiply(base2, modulus, nPrime);
} }
if ((nibble & 8UL) != 0UL) 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; remainingExponent >>= 4;
} }
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); return result.MontgomeryMultiply(1UL, modulus, nPrime);
} }
private static ulong Pow2MontgomeryModSlidingWindow(ulong exponent, in MontgomeryDivisorData divisor) private static ulong Pow2MontgomeryModSlidingWindow(ulong exponent, in MontgomeryDivisorData divisor)
@@ -816,16 +817,16 @@ public class Pow2ModBenchmarks
if (exponent == 0UL) if (exponent == 0UL)
{ {
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); return result.MontgomeryMultiply(1UL, modulus, nPrime);
} }
const int WindowSize = 5; const int WindowSize = 5;
Span<ulong> oddPowers = stackalloc ulong[1 << (WindowSize - 1)]; Span<ulong> oddPowers = stackalloc ulong[1 << (WindowSize - 1)];
oddPowers[0] = baseVal; 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++) 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); int bitIndex = 63 - BitOperations.LeadingZeroCount(exponent);
@@ -834,7 +835,7 @@ public class Pow2ModBenchmarks
{ {
if (((exponent >> bitIndex) & 1UL) == 0UL) if (((exponent >> bitIndex) & 1UL) == 0UL)
{ {
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime); result = result.MontgomeryMultiply(result, modulus, nPrime);
bitIndex--; bitIndex--;
continue; continue;
} }
@@ -851,14 +852,14 @@ public class Pow2ModBenchmarks
int squares = bitIndex - windowStart + 1; int squares = bitIndex - windowStart + 1;
for (int i = 0; i < squares; i++) 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; bitIndex = windowStart - 1;
} }
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); return result.MontgomeryMultiply(1UL, modulus, nPrime);
} }
private static ulong Pow2ModBinary(ulong exponent, ulong modulus) private static ulong Pow2ModBinary(ulong exponent, ulong modulus)
@@ -77,7 +77,7 @@ public class Pow2MontgomeryModBenchmarks
for (int i = 0; i < SampleCount; i++) for (int i = 0; i < SampleCount; i++)
{ {
checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertGpu(divisors[i]); checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
} }
return checksum; return checksum;
@@ -262,7 +262,7 @@ public class Pow2MontgomeryModBenchmarks
ulong nPrime = ComputeMontgomeryNPrime(modulus); ulong nPrime = ComputeMontgomeryNPrime(modulus);
ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus); ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus);
ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, 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( return new MontgomeryDivisorData(
modulus, modulus,
@@ -300,22 +300,22 @@ public class Pow2MontgomeryModBenchmarks
if (exponent == 0UL) if (exponent == 0UL)
{ {
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); return result.MontgomeryMultiply(1UL, modulus, nPrime);
} }
int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent); int msbIndex = 63 - BitOperations.LeadingZeroCount(exponent);
for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--) for (int bitIndex = msbIndex; bitIndex >= 0; bitIndex--)
{ {
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime); result = result.MontgomeryMultiply(result, modulus, nPrime);
if (((exponent >> bitIndex) & 1UL) != 0UL) 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) private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor)
@@ -336,35 +336,35 @@ public class Pow2MontgomeryModBenchmarks
ulong nibble = remainingExponent & 0xFUL; ulong nibble = remainingExponent & 0xFUL;
ulong base0 = baseVal; ulong base0 = baseVal;
ulong base1 = base0.MontgomeryMultiplyCpu(base0, modulus, nPrime); ulong base1 = base0.MontgomeryMultiply(base0, modulus, nPrime);
ulong base2 = base1.MontgomeryMultiplyCpu(base1, modulus, nPrime); ulong base2 = base1.MontgomeryMultiply(base1, modulus, nPrime);
ulong base3 = base2.MontgomeryMultiplyCpu(base2, modulus, nPrime); ulong base3 = base2.MontgomeryMultiply(base2, modulus, nPrime);
if ((nibble & 1UL) != 0UL) if ((nibble & 1UL) != 0UL)
{ {
result = result.MontgomeryMultiplyCpu(base0, modulus, nPrime); result = result.MontgomeryMultiply(base0, modulus, nPrime);
} }
if ((nibble & 2UL) != 0UL) if ((nibble & 2UL) != 0UL)
{ {
result = result.MontgomeryMultiplyCpu(base1, modulus, nPrime); result = result.MontgomeryMultiply(base1, modulus, nPrime);
} }
if ((nibble & 4UL) != 0UL) if ((nibble & 4UL) != 0UL)
{ {
result = result.MontgomeryMultiplyCpu(base2, modulus, nPrime); result = result.MontgomeryMultiply(base2, modulus, nPrime);
} }
if ((nibble & 8UL) != 0UL) 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; remainingExponent >>= 4;
} }
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); return result.MontgomeryMultiply(1UL, modulus, nPrime);
} }
// TODO: Update it with the latest implementations // TODO: Update it with the latest implementations
@@ -382,16 +382,16 @@ public class Pow2MontgomeryModBenchmarks
if (exponent == 0UL) if (exponent == 0UL)
{ {
return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime); return result.MontgomeryMultiply(1UL, modulus, nPrime);
} }
const int WindowSize = 5; const int WindowSize = 5;
Span<ulong> oddPowers = stackalloc ulong[1 << (WindowSize - 1)]; Span<ulong> oddPowers = stackalloc ulong[1 << (WindowSize - 1)];
oddPowers[0] = baseVal; 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++) 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); int bitIndex = 63 - BitOperations.LeadingZeroCount(exponent);
@@ -400,7 +400,7 @@ public class Pow2MontgomeryModBenchmarks
{ {
if (((exponent >> bitIndex) & 1UL) == 0UL) if (((exponent >> bitIndex) & 1UL) == 0UL)
{ {
result = result.MontgomeryMultiplyCpu(result, modulus, nPrime); result = result.MontgomeryMultiply(result, modulus, nPrime);
bitIndex--; bitIndex--;
continue; continue;
} }
@@ -417,14 +417,14 @@ public class Pow2MontgomeryModBenchmarks
int squares = bitIndex - windowStart + 1; int squares = bitIndex - windowStart + 1;
for (int i = 0; i < squares; i++) 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; 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 // 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; using PerfectNumbers.Core;
namespace EvenPerfectBitScanner.Benchmarks; namespace EvenPerfectBitScanner.Benchmarks;
@@ -1,3 +1,4 @@
using System;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using PerfectNumbers.Core; using PerfectNumbers.Core;
@@ -33,7 +34,7 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks
for (int i = 0; i < cases.Length; i++) for (int i = 0; i < cases.Length; i++)
{ {
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i]; ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
checksum ^= current.Exponent.Pow2MontgomeryModWindowedConvertGpu(current.Divisor); checksum ^= current.Exponent.Pow2MontgomeryModWindowedGpu(current.Divisor, keepMontgomery: false);
} }
return checksum; return checksum;
@@ -51,7 +52,7 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks
for (int i = 0; i < cases.Length; i++) for (int i = 0; i < cases.Length; i++)
{ {
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i]; ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
checksum ^= current.Exponent.Pow2MontgomeryModWindowedKeepGpu(current.Divisor); checksum ^= current.Exponent.Pow2MontgomeryModWindowedGpu(current.Divisor, keepMontgomery: true);
} }
return checksum; return checksum;
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Threading;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using Open.Numeric.Primes; using Open.Numeric.Primes;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace EvenPerfectBitScanner.Benchmarks; namespace EvenPerfectBitScanner.Benchmarks;
@@ -10,7 +12,8 @@ namespace EvenPerfectBitScanner.Benchmarks;
[MemoryDiagnoser] [MemoryDiagnoser]
public class PrimeTesterBenchmarks public class PrimeTesterBenchmarks
{ {
private static readonly HeuristicPrimeTester _heuristicTester = new(); private HeuristicPrimeTester _cpuTester = null!;
private HeuristicPrimeTester _gpuTester = null!;
private ulong[] _candidates = []; private ulong[] _candidates = [];
[ParamsSource(nameof(GetBenchmarkCases))] [ParamsSource(nameof(GetBenchmarkCases))]
@@ -19,6 +22,8 @@ public class PrimeTesterBenchmarks
[GlobalSetup] [GlobalSetup]
public void GlobalSetup() public void GlobalSetup()
{ {
_cpuTester = new HeuristicPrimeTester();
_gpuTester = new HeuristicPrimeTester();
_candidates = BenchmarkCase.Candidates; _candidates = BenchmarkCase.Candidates;
if (_candidates.Length == 0) if (_candidates.Length == 0)
@@ -26,23 +31,23 @@ public class PrimeTesterBenchmarks
return; return;
} }
PrimeOrderCalculatorAccelerator.WarmUp(); CancellationToken cancellationToken = CancellationToken.None;
// HeuristicGroupABPrimeTesterAccelerator.WarmUp(); _cpuTester.IsPrimeCpu(_candidates[0], cancellationToken);
_ = HeuristicPrimeTester.IsPrimeCpu(_candidates[0]); _gpuTester.IsPrimeGpu(_candidates[0]);
_ = HeuristicPrimeTester.IsPrimeGpu(_candidates[0]);
_ = Prime.Numbers.IsPrime(_candidates[0]); _ = Prime.Numbers.IsPrime(_candidates[0]);
} }
// [Benchmark] [Benchmark(Baseline = true)]
public int HeuristicCpu() public int HeuristicCpu()
{ {
HeuristicPrimeTester tester = _heuristicTester; HeuristicPrimeTester tester = _cpuTester;
ulong[] values = _candidates; ulong[] values = _candidates;
CancellationToken cancellationToken = CancellationToken.None;
int primeCount = 0; int primeCount = 0;
for (int i = 0; i < values.Length; i++) for (int i = 0; i < values.Length; i++)
{ {
if (HeuristicPrimeTester.IsPrimeCpu(values[i])) if (tester.IsPrimeCpu(values[i], cancellationToken))
{ {
primeCount++; primeCount++;
} }
@@ -54,13 +59,14 @@ public class PrimeTesterBenchmarks
[Benchmark] [Benchmark]
public int HeuristicGpu() public int HeuristicGpu()
{ {
HeuristicPrimeTester tester = _heuristicTester; HeuristicPrimeTester tester = _gpuTester;
ulong[] values = _candidates; ulong[] values = _candidates;
CancellationToken cancellationToken = CancellationToken.None;
int primeCount = 0; int primeCount = 0;
for (int i = 0; i < values.Length; i++) for (int i = 0; i < values.Length; i++)
{ {
if (HeuristicPrimeTester.IsPrimeGpu(values[i])) if (tester.IsPrimeGpu(values[i]))
{ {
primeCount++; primeCount++;
} }
@@ -69,49 +75,18 @@ public class PrimeTesterBenchmarks
return primeCount; return primeCount;
} }
[Benchmark] // This benchmark is very slow. No point in comparing it.
public int HeuristicCombinedGpu() // [Benchmark]
{
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]
public int NonHeuristicGpu() public int NonHeuristicGpu()
{ {
// PrimeTester tester = _gpuTester;
ulong[] values = _candidates; ulong[] values = _candidates;
CancellationToken cancellationToken = CancellationToken.None;
int primeCount = 0; int primeCount = 0;
for (int i = 0; i < values.Length; i++) for (int i = 0; i < values.Length; i++)
{ {
if (PrimeTester.IsPrimeGpu(values[i])) if (PrimeTester.Exclusive.IsPrimeGpu(values[i], cancellationToken))
{ {
primeCount++; primeCount++;
} }
@@ -120,15 +95,17 @@ public class PrimeTesterBenchmarks
return primeCount; return primeCount;
} }
[Benchmark(Baseline = true)] [Benchmark]
public int NonHeuristicCpu() public int NonHeuristicCpu()
{ {
// PrimeTester tester = _gpuTester;
ulong[] values = _candidates; ulong[] values = _candidates;
CancellationToken cancellationToken = CancellationToken.None;
int primeCount = 0; int primeCount = 0;
for (int i = 0; i < values.Length; i++) for (int i = 0; i < values.Length; i++)
{ {
if (PrimeTester.IsPrime(values[i])) if (PrimeTester.IsPrimeCpu(values[i], cancellationToken))
{ {
primeCount++; primeCount++;
} }
@@ -137,7 +114,7 @@ public class PrimeTesterBenchmarks
return primeCount; return primeCount;
} }
// [Benchmark] [Benchmark]
public int OpenNumericPrimes() public int OpenNumericPrimes()
{ {
ulong[] values = _candidates; ulong[] values = _candidates;
@@ -19,7 +19,7 @@ internal static class Program
"/help" "/help"
}; };
private const string DefaultHeader = "p,divisor,searchedMersenne,detailedCheck,passedAllTests"; private const string DefaultHeader = "p,searchedMersenne,detailedCheck,passedAllTests";
private static int Main(string[] args) private static int Main(string[] args)
{ {
+39 -1
View File
@@ -3,9 +3,13 @@ using Xunit;
using System.Reflection; using System.Reflection;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using EvenPerfectBitScanner;
using EvenPerfectBitScanner.Candidates; using EvenPerfectBitScanner.Candidates;
using EvenPerfectBitScanner.Candidates.Transforms; using EvenPerfectBitScanner.Candidates.Transforms;
using System.Numerics; using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
namespace EvenPerfectBitScanner.Tests; namespace EvenPerfectBitScanner.Tests;
@@ -51,7 +55,7 @@ public class ProgramTests
[InlineData(81UL, false)] [InlineData(81UL, false)]
public void IsPrime_identifies_primes_correctly(ulong n, bool expected) 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] [Fact]
@@ -98,11 +102,13 @@ public class ProgramTests
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!; var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", 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 primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
testerField.SetValue(null, new MersenneNumberDivisorGpuTester()); testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), 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)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
candidatesField.SetValue(null, new (ulong, uint)[] { (7UL, 3U), (23UL, 11U) }); candidatesField.SetValue(null, new (ulong, uint)[] { (7UL, 3U), (23UL, 11U) });
forceCpuProp!.SetValue(null, true);
try try
{ {
@@ -116,6 +122,7 @@ public class ProgramTests
candidatesField.SetValue(null, null); candidatesField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -128,11 +135,13 @@ public class ProgramTests
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!; var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", 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 primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
testerField.SetValue(null, new MersenneNumberDivisorGpuTester()); testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), 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)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
candidatesField.SetValue(null, Array.Empty<(ulong, uint)>()); candidatesField.SetValue(null, Array.Empty<(ulong, uint)>());
forceCpuProp!.SetValue(null, true);
try try
{ {
@@ -146,6 +155,7 @@ public class ProgramTests
candidatesField.SetValue(null, null); candidatesField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -158,10 +168,12 @@ public class ProgramTests
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!; var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", 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()); testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), 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)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, true);
try try
{ {
@@ -174,6 +186,7 @@ public class ProgramTests
testerField.SetValue(null, null); testerField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -186,10 +199,12 @@ public class ProgramTests
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!; var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", 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()); testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), 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)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -206,6 +221,7 @@ public class ProgramTests
testerField.SetValue(null, null); testerField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -217,10 +233,12 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", 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)); 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)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -237,6 +255,7 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -248,6 +267,7 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", 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( mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useIncremental: true, useIncremental: true,
@@ -255,6 +275,7 @@ public class ProgramTests
maxK: 1_024UL), trackAllValues: true)); maxK: 1_024UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), 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)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -271,6 +292,7 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -283,6 +305,7 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", 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( mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useIncremental: true, useIncremental: true,
@@ -290,6 +313,7 @@ public class ProgramTests
maxK: 5_000_000UL), trackAllValues: true)); maxK: 5_000_000UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), 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)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -306,6 +330,7 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -317,6 +342,7 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", 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( mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useIncremental: true, useIncremental: true,
@@ -324,6 +350,7 @@ public class ProgramTests
maxK: 5_000_000UL), trackAllValues: true)); maxK: 5_000_000UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), 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)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -340,6 +367,7 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -352,6 +380,7 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", 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( mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useIncremental: true, useIncremental: true,
@@ -361,6 +390,7 @@ public class ProgramTests
maxK: 1_024UL), trackAllValues: true)); maxK: 1_024UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), 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)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
forceCpuProp!.SetValue(null, true);
try try
{ {
@@ -372,6 +402,7 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -386,6 +417,7 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", 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( mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useIncremental: false, useIncremental: false,
@@ -396,6 +428,7 @@ public class ProgramTests
maxK: configuration.ResidueMaxK), trackAllValues: true)); maxK: configuration.ResidueMaxK), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), 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)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -407,7 +440,9 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
GpuContextPool.DisposeAll();
} }
} }
@@ -471,10 +506,12 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", 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 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)); 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)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, true);
try try
{ {
@@ -495,6 +532,7 @@ public class ProgramTests
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
compositeField.SetValue(null, false); compositeField.SetValue(null, false);
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -1,3 +1,6 @@
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
@@ -1,13 +1,15 @@
using System.Buffers.Text; using System.Buffers.Text;
using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading;
using PerfectNumbers.Core; using PerfectNumbers.Core;
namespace EvenPerfectBitScanner; namespace EvenPerfectBitScanner;
internal static class CalculationResultHandler 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)"; private const string PrimeFoundSuffix = " (FOUND VALID CANDIDATES)";
internal const int DefaultWriteBatchSize = 100; internal const int DefaultWriteBatchSize = 100;
@@ -93,19 +95,19 @@ internal static class CalculationResultHandler
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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. // Program flow ensures Initialize(...) and InitializeOutputBuffer() ran before this call. Do not add initialization guards here.
Span<byte> stackBuffer = stackalloc byte[128]; Span<byte> stackBuffer = stackalloc byte[128];
byte[]? rentedBuffer = null; byte[]? rentedBuffer = null;
Span<byte> utf8Span = stackBuffer; Span<byte> utf8Span = stackBuffer;
int byteCount = TryFormatResultLine(utf8Span, currentP, divisor, searchedMersenne, detailedCheck, passedAllTests); int byteCount = TryFormatResultLine(utf8Span, currentP, searchedMersenne, detailedCheck, passedAllTests);
if (byteCount < 0) if (byteCount < 0)
{ {
rentedBuffer = ThreadStaticPools.BytePool.Rent(256); rentedBuffer = ThreadStaticPools.BytePool.Rent(256);
utf8Span = rentedBuffer; utf8Span = rentedBuffer;
byteCount = TryFormatResultLine(utf8Span, currentP, divisor, searchedMersenne, detailedCheck, passedAllTests); byteCount = TryFormatResultLine(utf8Span, currentP, searchedMersenne, detailedCheck, passedAllTests);
if (byteCount < 0) if (byteCount < 0)
{ {
ThreadStaticPools.BytePool.Return(rentedBuffer); ThreadStaticPools.BytePool.Return(rentedBuffer);
@@ -229,7 +231,7 @@ internal static class CalculationResultHandler
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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; int offset = 0;
@@ -246,19 +248,6 @@ internal static class CalculationResultHandler
destination[offset++] = (byte)','; 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)) if (!Utf8Formatter.TryFormat(searchedMersenne, destination[offset..], out written))
{ {
return -1; return -1;
@@ -1,4 +1,5 @@
using System.Globalization; using System.Globalization;
using System.IO;
namespace EvenPerfectBitScanner.Candidates; namespace EvenPerfectBitScanner.Candidates;
@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using Open.Numeric.Primes; using Open.Numeric.Primes;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using EvenPerfectBitScanner.Candidates.Transforms; using EvenPerfectBitScanner.Candidates.Transforms;
@@ -1,4 +1,6 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using EvenPerfectBitScanner.Candidates;
using Open.Numeric.Primes; using Open.Numeric.Primes;
namespace EvenPerfectBitScanner.Candidates.Transforms; namespace EvenPerfectBitScanner.Candidates.Transforms;
@@ -1,4 +1,6 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using EvenPerfectBitScanner.Candidates;
namespace EvenPerfectBitScanner.Candidates.Transforms; namespace EvenPerfectBitScanner.Candidates.Transforms;
@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
namespace EvenPerfectBitScanner.Candidates.Transforms; namespace EvenPerfectBitScanner.Candidates.Transforms;
+36 -45
View File
@@ -1,4 +1,6 @@
using System;
using System.Globalization; using System.Globalization;
using System.Numerics;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
@@ -200,15 +202,14 @@ internal readonly struct CliArguments
if (argument.StartsWith("--prime=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--prime=", StringComparison.OrdinalIgnoreCase))
{ {
ReadOnlySpan<char> primeValue = argument.AsSpan("--prime=".Length); startPrime = Utf8CliParser.ParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1));
startPrime = Utf8CliParser.ParseUInt64(primeValue);
startPrimeProvided = true; startPrimeProvided = true;
continue; continue;
} }
if (argument.StartsWith("--max-prime=", StringComparison.OrdinalIgnoreCase)) 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)) if (UInt128.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out UInt128 parsedMaxPrime))
{ {
maxPrimeLimit = parsedMaxPrime; maxPrimeLimit = parsedMaxPrime;
@@ -237,13 +238,13 @@ internal readonly struct CliArguments
if (argument.StartsWith("--threads=", StringComparison.OrdinalIgnoreCase)) 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; continue;
} }
if (argument.StartsWith("--mersenne=", StringComparison.OrdinalIgnoreCase)) 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)) if (value.Equals("pow2mod", StringComparison.OrdinalIgnoreCase))
{ {
kernelType = GpuKernelType.Pow2Mod; kernelType = GpuKernelType.Pow2Mod;
@@ -280,7 +281,7 @@ internal readonly struct CliArguments
if (argument.StartsWith("--divisor-cycles-limit=", StringComparison.OrdinalIgnoreCase)) 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; divisorCyclesSearchLimit = parsedLimit;
} }
@@ -290,7 +291,7 @@ internal readonly struct CliArguments
if (argument.StartsWith("--residue-max-k=", StringComparison.OrdinalIgnoreCase)) 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; residueKMax = parsedResidueMax;
} }
@@ -300,7 +301,7 @@ internal readonly struct CliArguments
if (argument.StartsWith("--mersenne-device=", StringComparison.OrdinalIgnoreCase)) 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; continue;
} }
@@ -318,7 +319,7 @@ internal readonly struct CliArguments
if (argument.StartsWith("--order-warmup-limit=", StringComparison.OrdinalIgnoreCase)) 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; orderWarmupLimitOverride = parsedLimit;
} }
@@ -334,14 +335,14 @@ internal readonly struct CliArguments
if (argument.StartsWith("--ntt=", StringComparison.OrdinalIgnoreCase)) 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; nttBackend = value.Equals("staged", StringComparison.OrdinalIgnoreCase) ? NttBackend.Staged : NttBackend.Reference;
continue; continue;
} }
if (argument.StartsWith("--mod-reduction=", StringComparison.OrdinalIgnoreCase)) 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)) if (value.Equals("mont64", StringComparison.OrdinalIgnoreCase))
{ {
modReductionMode = ModReductionMode.Mont64; modReductionMode = ModReductionMode.Mont64;
@@ -364,25 +365,25 @@ internal readonly struct CliArguments
if (argument.StartsWith("--primes-device=", StringComparison.OrdinalIgnoreCase)) 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; continue;
} }
if (argument.StartsWith("--order-device=", StringComparison.OrdinalIgnoreCase)) 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; continue;
} }
if (argument.StartsWith("--rle-blacklist=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--rle-blacklist=", StringComparison.OrdinalIgnoreCase))
{ {
rleBlacklistPath = argument["--rle-blacklist=".Length..]; rleBlacklistPath = argument[(argument.IndexOf('=') + 1)..];
continue; continue;
} }
if (argument.StartsWith("--rle-hard-max=", StringComparison.OrdinalIgnoreCase)) 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; rleHardMaxP = parsedMax;
} }
@@ -392,14 +393,14 @@ internal readonly struct CliArguments
if (argument.StartsWith("--rle-only-last7=", StringComparison.OrdinalIgnoreCase)) 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); rleOnlyLast7 = !value.Equals("false", StringComparison.OrdinalIgnoreCase) && !value.Equals("0", StringComparison.OrdinalIgnoreCase);
continue; continue;
} }
if (argument.StartsWith("--zero-hard=", StringComparison.OrdinalIgnoreCase)) 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; zeroFractionHard = zeroFraction;
} }
@@ -409,7 +410,7 @@ internal readonly struct CliArguments
if (argument.StartsWith("--zero-conj=", StringComparison.OrdinalIgnoreCase)) 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(':'); int colon = value.IndexOf(':');
if (colon > 0) if (colon > 0)
{ {
@@ -426,97 +427,87 @@ internal readonly struct CliArguments
if (argument.StartsWith("--lucas=", StringComparison.OrdinalIgnoreCase)) 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; 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); forcePrimeKernelsOnCpu = argument.AsSpan(argument.IndexOf('=') + 1).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);
continue; continue;
} }
if (argument.StartsWith("--results-dir=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--results-dir=", StringComparison.OrdinalIgnoreCase))
{ {
resultsDirectory = argument["--results-dir=".Length..]; resultsDirectory = argument[(argument.IndexOf('=') + 1)..];
continue; continue;
} }
if (argument.StartsWith("--results-prefix=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--results-prefix=", StringComparison.OrdinalIgnoreCase))
{ {
resultsPrefix = argument["--results-prefix=".Length..]; resultsPrefix = argument[(argument.IndexOf('=') + 1)..];
continue; continue;
} }
if (argument.StartsWith("--gpu-prime-threads=", StringComparison.OrdinalIgnoreCase)) 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; continue;
} }
if (argument.StartsWith("--gpu-prime-batch=", StringComparison.OrdinalIgnoreCase)) 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; continue;
} }
if (argument.StartsWith("--ll-slice=", StringComparison.OrdinalIgnoreCase)) 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; continue;
} }
if (argument.StartsWith("--gpu-scan-batch=", StringComparison.OrdinalIgnoreCase)) 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; continue;
} }
if (argument.StartsWith("--block-size=", StringComparison.OrdinalIgnoreCase)) 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; continue;
} }
if (argument.StartsWith("--filter-p=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--filter-p=", StringComparison.OrdinalIgnoreCase))
{ {
filterFile = argument["--filter-p=".Length..]; filterFile = argument[(argument.IndexOf('=') + 1)..];
continue; continue;
} }
if (argument.StartsWith("--write-batch-size=", StringComparison.OrdinalIgnoreCase)) 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; continue;
} }
if (argument.StartsWith("--divisor-cycles=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--divisor-cycles=", StringComparison.OrdinalIgnoreCase))
{ {
cyclesPath = argument["--divisor-cycles=".Length..]; cyclesPath = argument[(argument.IndexOf('=') + 1)..];
continue; continue;
} }
if (argument.StartsWith("--divisor-cycles-device=", StringComparison.OrdinalIgnoreCase)) 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; continue;
} }
if (argument.StartsWith("--divisor-cycles-batch=", StringComparison.OrdinalIgnoreCase)) 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; continue;
} }
@@ -1,6 +1,7 @@
using Open.Collections; using Open.Collections;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using EvenPerfectBitScanner;
namespace EvenPerfectBitScanner.IO; namespace EvenPerfectBitScanner.IO;
@@ -13,7 +14,7 @@ internal static class CalculationResultsFile
using FileStream readStream = new(candidateFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); using FileStream readStream = new(candidateFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using StreamReader reader = new(readStream); using StreamReader reader = new(readStream);
string? line; string? line;
ReadOnlySpan<char> trimmed; ReadOnlySpan<char> span;
while ((line = reader.ReadLine()) is not null) while ((line = reader.ReadLine()) is not null)
{ {
if (string.IsNullOrWhiteSpace(line)) if (string.IsNullOrWhiteSpace(line))
@@ -21,60 +22,30 @@ internal static class CalculationResultsFile
continue; continue;
} }
trimmed = line.AsSpan().Trim(); span = line.AsSpan().Trim();
if (trimmed.IsEmpty || trimmed[0] == '#') if (span.IsEmpty || span[0] == '#')
{ {
continue; continue;
} }
int length = trimmed.Length;
int index = 0; 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++; index++;
continue; continue;
} }
int start = index++; int start = index;
while (index < length && (uint)(trimmed[index] - '0') <= 9U) index++;
while (index < span.Length && char.IsDigit(span[index]))
{ {
index++; index++;
} }
if (Utf8CliParser.TryParseUInt64(trimmed[start..index], out ulong parsed)) if (Utf8CliParser.TryParseUInt64(span[start..index], out ulong parsed))
{ {
candidates.Add(parsed); if (!maxPrimeConfigured || (UInt128)parsed <= maxPrimeLimit)
}
}
}
else
{
UInt128 limit = maxPrimeLimit;
while (index < length)
{
char current = trimmed[index];
uint digit = (uint)(current - '0');
if (digit > 9U)
{
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); candidates.Add(parsed);
} }
@@ -85,7 +56,6 @@ internal static class CalculationResultsFile
} }
} }
} }
}
return candidates; return candidates;
} }
@@ -100,7 +70,6 @@ internal static class CalculationResultsFile
int first = 0; int first = 0;
int second = 0; int second = 0;
int third = 0; int third = 0;
int fourth = 0;
ulong parsedP = 0UL; ulong parsedP = 0UL;
ReadOnlySpan<char> detailedSpan = default; ReadOnlySpan<char> detailedSpan = default;
ReadOnlySpan<char> passedAllTestsSpan = default; ReadOnlySpan<char> passedAllTestsSpan = default;
@@ -140,15 +109,8 @@ internal static class CalculationResultsFile
continue; continue;
} }
span = span[(third + 1)..]; detailedSpan = span[..third];
fourth = span.IndexOf(','); passedAllTestsSpan = span[(third + 1)..];
if (fourth < 0)
{
continue;
}
detailedSpan = span[..fourth];
passedAllTestsSpan = span[(fourth + 1)..];
if (Utf8CliParser.TryParseBoolean(detailedSpan, out detailed) && Utf8CliParser.TryParseBoolean(passedAllTestsSpan, out passedAllTests)) if (Utf8CliParser.TryParseBoolean(detailedSpan, out detailed) && Utf8CliParser.TryParseBoolean(passedAllTestsSpan, out passedAllTests))
{ {
+28 -23
View File
@@ -12,7 +12,7 @@ namespace EvenPerfectBitScanner;
internal static class Program internal static class Program
{ {
// private static ThreadLocal<PrimeTester> PrimeTesters = null!; private static ThreadLocal<PrimeTester> PrimeTesters = null!;
private static ThreadLocal<MersenneNumberTester> MersenneTesters = null!; private static ThreadLocal<MersenneNumberTester> MersenneTesters = null!;
private const ulong InitialP = PerfectNumberConstants.BiggestKnownEvenPerfectP; private const ulong InitialP = PerfectNumberConstants.BiggestKnownEvenPerfectP;
private static MersenneNumberDivisorGpuTester? _divisorTester; private static MersenneNumberDivisorGpuTester? _divisorTester;
@@ -28,8 +28,6 @@ internal static class Program
[ThreadStatic] [ThreadStatic]
private static bool _lastCompositeP; private static bool _lastCompositeP;
private static bool _runPrimesOnCpu;
private static void Main(string[] args) private static void Main(string[] args)
{ {
try try
@@ -51,24 +49,18 @@ internal static class Program
NttGpuMath.GpuTransformBackend = _cliArguments.NttBackend; NttGpuMath.GpuTransformBackend = _cliArguments.NttBackend;
NttGpuMath.ReductionMode = _cliArguments.ModReductionMode; NttGpuMath.ReductionMode = _cliArguments.ModReductionMode;
_runPrimesOnCpu = _cliArguments.ForcePrimeKernelsOnCpu; GpuContextPool.ForceCpu = _cliArguments.ForcePrimeKernelsOnCpu;
ulong currentP = _cliArguments.StartPrime; ulong currentP = _cliArguments.StartPrime;
ulong remainder = currentP % 6UL; ulong remainder = currentP % 6UL;
bool startPrimeProvided = _cliArguments.StartPrimeProvided; bool startPrimeProvided = _cliArguments.StartPrimeProvided;
int threadCount = Math.Max(1, _cliArguments.ThreadCount); 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); 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..."); Console.WriteLine("Starting up threads...");
_ = UnboundedTaskScheduler.Instance; _ = UnboundedTaskScheduler.Instance;
int blockSize = Math.Max(1, _cliArguments.BlockSize); 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; GpuKernelType kernelType = _cliArguments.KernelType;
bool useBitTransform = _cliArguments.UseBitTransform; bool useBitTransform = _cliArguments.UseBitTransform;
bool useOrder = _cliArguments.UseOrder; bool useOrder = _cliArguments.UseOrder;
@@ -112,7 +104,14 @@ internal static class Program
testPrimeCandidateLimit = Math.Max(1, threadCount) * 3; 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)) if (useByDivisor && !testMode && string.IsNullOrEmpty(filterFile))
{ {
@@ -120,6 +119,9 @@ internal static class Program
return; return;
} }
// Apply GPU prime sieve runtime configuration
GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads);
PrimeTester.GpuBatchSize = Math.Max(1, gpuPrimeBatch);
MersenneDivisorCycles mersenneDivisorCycles = new(); MersenneDivisorCycles mersenneDivisorCycles = new();
if (!File.Exists(cyclesPath) || continueCyclesGeneration) if (!File.Exists(cyclesPath) || continueCyclesGeneration)
@@ -175,10 +177,13 @@ internal static class Program
Console.WriteLine("Divisor cycles are ready"); Console.WriteLine("Divisor cycles are ready");
blockSize = useByDivisor ? 1 : blockSize; if (useByDivisor)
{
blockSize = 1;
}
CandidatesCalculator.Initialize(currentP); 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. // Note: --primes-device controls default device for library kernels; p primality remains CPU here.
// Initialize per-thread p residue tracker (Identity model) at currentP // Initialize per-thread p residue tracker (Identity model) at currentP
if (!useDivisor && !_cliArguments.UseByDivisor) if (!useDivisor && !_cliArguments.UseByDivisor)
@@ -261,7 +266,7 @@ internal static class Program
orderWarmupLimitOverride ?? 5_000_000UL, orderWarmupLimitOverride ?? 5_000_000UL,
NttGpuMath.ReductionMode, NttGpuMath.ReductionMode,
mersenneOnGpu ? "gpu" : "cpu", mersenneOnGpu ? "gpu" : "cpu",
_runPrimesOnCpu ? "cpu" : "gpu", GpuContextPool.ForceCpu ? "cpu" : "gpu",
orderOnGpu ? "gpu" : "cpu"); orderOnGpu ? "gpu" : "cpu");
if (!string.IsNullOrEmpty(_cliArguments.ResultsPrefix)) if (!string.IsNullOrEmpty(_cliArguments.ResultsPrefix))
@@ -422,7 +427,7 @@ internal static class Program
_byDivisorStartPrime, _byDivisorStartPrime,
static () => _lastCompositeP = true, static () => _lastCompositeP = true,
static () => _lastCompositeP = false, 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, threadCount,
byDivisorPrimeRange); byDivisorPrimeRange);
@@ -483,7 +488,7 @@ internal static class Program
{ {
p = buffer[j]; p = buffer[j];
passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck); passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck);
CalculationResultHandler.HandleResult(p, 0UL, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP); CalculationResultHandler.HandleResult(p, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP);
} }
continue; continue;
@@ -506,7 +511,7 @@ internal static class Program
} }
passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck); 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) 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 primes-device=gpu, route p primality through GPU-assisted sieve with deterministic MR validation.
if (!_cliArguments.UseByDivisor) if (!_cliArguments.UseByDivisor)
{ {
if (!(_runPrimesOnCpu if (!(GpuContextPool.ForceCpu
? PrimeTester.IsPrime(p) ? PrimeTesters.Value!.IsPrime(p, CancellationToken.None)
: HeuristicCombinedPrimeTester.IsPrimeGpu(p))) : PrimeTesters.Value!.IsPrimeGpu(p, CancellationToken.None)))
{ {
_lastCompositeP = true; _lastCompositeP = true;
return false; return false;
@@ -649,7 +654,7 @@ internal static class Program
if (_cliArguments.UseByDivisor) if (_cliArguments.UseByDivisor)
{ {
// Windowed pow2mod kernel handles by-divisor scans. // 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); detailedCheck = MersenneTesters.Value!.IsMersennePrime(p);
+1
View File
@@ -1,3 +1,4 @@
using System;
using System.Buffers; using System.Buffers;
using System.Buffers.Text; using System.Buffers.Text;
using System.Text; using System.Text;
@@ -1,3 +1,7 @@
using FluentAssertions;
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu;
using Xunit; using Xunit;
namespace PerfectNumbers.Core.Tests.Gpu; namespace PerfectNumbers.Core.Tests.Gpu;
@@ -5,88 +9,88 @@ namespace PerfectNumbers.Core.Tests.Gpu;
[Collection("GpuNtt")] [Collection("GpuNtt")]
public class GpuContextPoolTests public class GpuContextPoolTests
{ {
// [Fact] [Fact]
// [Trait("Category", "Fast")] [Trait("Category", "Fast")]
// public void RentPreferred_cpu_returns_cpu_accelerator() public void RentPreferred_cpu_returns_cpu_accelerator()
// { {
// GpuContextPool.DisposeAll(); GpuContextPool.DisposeAll();
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true); GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
// try try
// { {
// lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU); lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU);
// } }
// finally finally
// { {
// lease.Dispose(); lease.Dispose();
// } }
// GpuContextPool.DisposeAll(); GpuContextPool.DisposeAll();
// } }
// [Fact] [Fact]
// [Trait("Category", "Fast")] [Trait("Category", "Fast")]
// public void RentPreferred_reuses_cpu_context() public void RentPreferred_reuses_cpu_context()
// { {
// GpuContextPool.DisposeAll(); GpuContextPool.DisposeAll();
// Context firstContext; Context firstContext;
// Accelerator firstAccelerator; Accelerator firstAccelerator;
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true); GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
// try try
// { {
// firstContext = lease.Context; firstContext = lease.Context;
// firstAccelerator = lease.Accelerator; firstAccelerator = lease.Accelerator;
// } }
// finally finally
// { {
// lease.Dispose(); lease.Dispose();
// } }
// lease = GpuContextPool.RentPreferred(preferCpu: true); lease = GpuContextPool.RentPreferred(preferCpu: true);
// try try
// { {
// lease.Context.Should().BeSameAs(firstContext); lease.Context.Should().BeSameAs(firstContext);
// lease.Accelerator.Should().BeSameAs(firstAccelerator); lease.Accelerator.Should().BeSameAs(firstAccelerator);
// } }
// finally finally
// { {
// lease.Dispose(); lease.Dispose();
// } }
// GpuContextPool.DisposeAll(); GpuContextPool.DisposeAll();
// } }
// [Fact] [Fact]
// [Trait("Category", "Fast")] [Trait("Category", "Fast")]
// public void DisposeAll_clears_pooled_contexts() public void DisposeAll_clears_pooled_contexts()
// { {
// GpuContextPool.DisposeAll(); GpuContextPool.DisposeAll();
// Context firstContext; Context firstContext;
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true); GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
// try try
// { {
// firstContext = lease.Context; firstContext = lease.Context;
// } }
// finally finally
// { {
// lease.Dispose(); lease.Dispose();
// } }
// GpuContextPool.DisposeAll(); GpuContextPool.DisposeAll();
// lease = GpuContextPool.RentPreferred(preferCpu: true); lease = GpuContextPool.RentPreferred(preferCpu: true);
// try try
// { {
// lease.Context.Should().NotBeSameAs(firstContext); lease.Context.Should().NotBeSameAs(firstContext);
// } }
// finally finally
// { {
// lease.Dispose(); lease.Dispose();
// } }
// GpuContextPool.DisposeAll(); GpuContextPool.DisposeAll();
// } }
} }
@@ -1,8 +1,8 @@
using System.Reflection; using System.Reflection;
using FluentAssertions;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit; using Xunit;
namespace PerfectNumbers.Core.Tests.Gpu; namespace PerfectNumbers.Core.Tests.Gpu;
@@ -114,10 +114,14 @@ public class GpuKernelCompilationTests
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModKernel", BindingFlags.NonPublic | BindingFlags.Static) typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModKernel not found."); ?? 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 = private static readonly Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> MersenneDivisorCyclesKernel =
DivisorCycleKernels.GpuDivisorCycleKernel; 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; PrimeTesterKernels.SmallPrimeSieveKernel;
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<byte>> PrimeTesterSharesFactorKernel = private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<byte>> PrimeTesterSharesFactorKernel =
@@ -163,23 +167,23 @@ public class GpuKernelCompilationTests
return new object[] { name, loader }; return new object[] { name, loader };
} }
// [Theory] [Theory]
// [Trait("Category", "Fast")] [Trait("Category", "Fast")]
// [MemberData(nameof(KernelLoaders))] [MemberData(nameof(KernelLoaders))]
// public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile) public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile)
// { {
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true); GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
// try try
// { {
// var accelerator = lease.Accelerator; var accelerator = lease.Accelerator;
// Action action = () => compile(accelerator); Action action = () => compile(accelerator);
// action.Should().NotThrow($"kernel {name} should compile"); action.Should().NotThrow($"kernel {name} should compile");
// } }
// finally finally
// { {
// lease.Dispose(); lease.Dispose();
// } }
// } }
private static void CompileDivisorCycleCacheKernel(Accelerator accelerator) private static void CompileDivisorCycleCacheKernel(Accelerator accelerator)
{ {
@@ -321,67 +325,56 @@ public class GpuKernelCompilationTests
private static void CompilePrimeOrderPow2ModWideKernel(Accelerator accelerator) private static void CompilePrimeOrderPow2ModWideKernel(Accelerator accelerator)
{ {
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>>(PrimeOrderGpuHeuristics.Pow2ModKernelWide); _ = PrimeOrderGetPow2ModWideKernelMethod.Invoke(null, new object[] { accelerator });
_ = kernel;
} }
private static void CompileMersenneDivisorCyclesKernel(Accelerator 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; _ = kernel;
} }
private static void CompilePrimeTesterSmallPrimeKernel(Accelerator accelerator) private static void CompilePrimeTesterSmallPrimeKernel(Accelerator accelerator)
{ {
var kernel = accelerator.LoadAutoGroupedStreamKernel(PrimeTesterSmallPrimeKernel); var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>>(PrimeTesterSmallPrimeKernel);
_ = kernel; _ = kernel;
} }
private static void CompilePrimeTesterSharesFactorKernel(Accelerator accelerator) private static void CompilePrimeTesterSharesFactorKernel(Accelerator accelerator)
{ {
var kernel = accelerator.LoadAutoGroupedStreamKernel(PrimeTesterSharesFactorKernel); var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterSharesFactorKernel);
_ = kernel; _ = 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;
GpuPrimeWorkLimiter.Acquire(); var lease = GpuKernelPool.GetKernel(useGpuOrder: false);
int acceleratorIndex = AcceleratorPool.Shared.Rent();
AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex);
try try
{ {
var kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, _kernelType); _ = lease.OrderKernel;
_ = kernels.Order; _ = lease.IncrementalKernel;
_ = kernels.Incremental; _ = lease.Pow2ModKernel;
_ = kernels.Pow2Mod; _ = lease.IncrementalOrderKernel;
_ = kernels.IncrementalOrder; _ = lease.Pow2ModOrderKernel;
_ = kernels.Pow2ModOrder;
} }
finally finally
{ {
stream.Synchronize(); lease.Dispose();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
// GpuPrimeWorkLimiter.Release();
} }
stream = AcceleratorStreamPool.Rent(acceleratorIndex); var orderLease = GpuKernelPool.GetKernel(useGpuOrder: true);
GpuPrimeWorkLimiter.Acquire();
try try
{ {
var kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, _kernelType); _ = orderLease.OrderKernel;
_ = kernels.Order; _ = orderLease.IncrementalKernel;
_ = kernels.Incremental; _ = orderLease.Pow2ModKernel;
_ = kernels.Pow2Mod; _ = orderLease.IncrementalOrderKernel;
_ = kernels.IncrementalOrder; _ = orderLease.Pow2ModOrderKernel;
_ = kernels.Pow2ModOrder;
} }
finally finally
{ {
stream.Synchronize(); orderLease.Dispose();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
// GpuPrimeWorkLimiter.Release();
} }
} }
} }
@@ -18,33 +18,22 @@ public class GpuPrimeLimiterTests
var startedSecond = new TaskCompletionSource<bool>(); var startedSecond = new TaskCompletionSource<bool>();
var sw = new Stopwatch(); var sw = new Stopwatch();
GpuPrimeWorkLimiter.Acquire(); using var first = GpuPrimeWorkLimiter.Acquire();
sw.Start(); sw.Start();
var secondTask = Task.Run(() => var secondTask = Task.Run(() =>
{ {
GpuPrimeWorkLimiter.Acquire(); using var second = GpuPrimeWorkLimiter.Acquire();
try
{
startedSecond.TrySetResult(true); startedSecond.TrySetResult(true);
}
finally
{
GpuPrimeWorkLimiter.Release();
}
}); });
try
{
// Give secondTask a moment to attempt acquire (it should block). // Give secondTask a moment to attempt acquire (it should block).
await Task.Delay(50); await Task.Delay(50);
startedSecond.Task.IsCompleted.Should().BeFalse(); startedSecond.Task.IsCompleted.Should().BeFalse();
}
finally // Release first after short delay, allowing second to continue.
{ // (Dispose at scope end, but we want to end now.)
// Release first after the initial wait so the second task can proceed. first.Dispose();
GpuPrimeWorkLimiter.Release();
}
await secondTask.WaitAsync(TimeSpan.FromSeconds(1)); await secondTask.WaitAsync(TimeSpan.FromSeconds(1));
sw.Stop(); sw.Stop();
@@ -14,7 +14,7 @@ public class LucasLehmerPrimeTesterTests
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
public void IsMersennePrime_gpu_returns_expected_results(ulong p, bool expected) 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] [Fact]
@@ -26,7 +26,7 @@ public class LucasLehmerPrimeTesterTests
const ulong exponent = 128UL; const ulong exponent = 128UL;
var tester = new MersenneNumberLucasLehmerGpuTester(); var tester = new MersenneNumberLucasLehmerGpuTester();
tester.WarmUpNttParameters(exponent); tester.WarmUpNttParameters(exponent);
tester.IsPrime(exponent, true).Should().BeFalse(); tester.IsMersennePrime(exponent).Should().BeFalse();
} }
[Fact] [Fact]
@@ -49,7 +49,7 @@ public class LucasLehmerPrimeTesterTests
{ {
var tester = new MersenneNumberLucasLehmerGpuTester(); var tester = new MersenneNumberLucasLehmerGpuTester();
tester.WarmUpNttParameters(exponent); tester.WarmUpNttParameters(exponent);
tester.IsPrime(exponent, true).Should().BeFalse(); tester.IsMersennePrime(exponent).Should().BeFalse();
} }
[Theory] [Theory]
@@ -150,8 +150,10 @@ public class NttGpuMathTests
var modulus = new GpuUInt128(0UL, 17UL); var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL); var primitiveRoot = new GpuUInt128(0UL, 3UL);
var accelerator = SharedGpuContext.CreateAccelerator(); GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
var stream = accelerator.CreateStream(); try
{
var accelerator = gpu.Accelerator;
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static); var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
method.Should().NotBeNull(); method.Should().NotBeNull();
@@ -166,11 +168,8 @@ public class NttGpuMathTests
int count = 7; // length 8 -> 7 twiddles int count = 7; // length 8 -> 7 twiddles
var forward = new GpuUInt128[count]; var forward = new GpuUInt128[count];
var inverse = new GpuUInt128[count]; var inverse = new GpuUInt128[count];
twiddlesMont.CopyToCPU(stream, forward); twiddlesMont.CopyToCPU(forward);
twiddlesInvMont.CopyToCPU(stream, inverse); twiddlesInvMont.CopyToCPU(inverse);
stream.Synchronize();
stream.Dispose();
accelerator.Dispose();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
@@ -178,6 +177,11 @@ public class NttGpuMathTests
prod.Should().Be(r2); prod.Should().Be(r2);
} }
} }
finally
{
gpu.Dispose();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -341,15 +345,24 @@ public class NttGpuMathTests
values[1] = new GpuUInt128(0UL, 2UL); values[1] = new GpuUInt128(0UL, 2UL);
var modulus = new GpuUInt128(0UL, 17UL); var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL); var primitiveRoot = new GpuUInt128(0UL, 3UL);
Accelerator accelerator = SharedGpuContext.CreateAccelerator(); GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
var stream = accelerator.CreateStream(); try
var buffer = accelerator.Allocate1D<GpuUInt128>(values.Length); {
buffer.View.CopyFromCPU(stream, ref values[0], values.Length); var buffer = gpu.Accelerator.Allocate1D<GpuUInt128>(values.Length);
NttGpuMath.SquareDevice(accelerator, stream, buffer.View, modulus, primitiveRoot); try
stream.Synchronize(); {
stream.Dispose(); buffer.View.CopyFromCPU(ref values[0], values.Length);
NttGpuMath.SquareDevice(gpu.Accelerator, buffer.View, modulus, primitiveRoot);
}
finally
{
buffer.Dispose(); buffer.Dispose();
accelerator.Dispose(); }
}
finally
{
gpu.Dispose();
}
// Verify that the square cache now holds entries for the accelerator. // Verify that the square cache now holds entries for the accelerator.
var field = typeof(NttGpuMath).GetField("SquareCache", BindingFlags.NonPublic | BindingFlags.Static)!; var field = typeof(NttGpuMath).GetField("SquareCache", BindingFlags.NonPublic | BindingFlags.Static)!;
@@ -357,6 +370,8 @@ public class NttGpuMathTests
int count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!; int count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
count.Should().BeGreaterThan(0); count.Should().BeGreaterThan(0);
GpuContextPool.DisposeAll();
count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!; count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
count.Should().Be(0); count.Should().Be(0);
} }
@@ -33,21 +33,24 @@ public class NttMontgomeryTests
NttGpuMath.Square(cpu, modulus, primitiveRoot); NttGpuMath.Square(cpu, modulus, primitiveRoot);
// Device execution // Device execution
var accelerator = SharedGpuContext.CreateAccelerator(); GpuContextPool.GpuContextLease context = GpuContextPool.Rent();
var stream = accelerator.CreateStream(); try
{
var accelerator = context.Accelerator;
using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length); using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
buffer.View.CopyFromCPU(stream, ref gpu[0], gpu.Length); buffer.View.CopyFromCPU(ref gpu[0], gpu.Length);
NttGpuMath.SquareDevice(accelerator, stream, buffer.View, modulus, primitiveRoot); NttGpuMath.SquareDevice(accelerator, buffer.View, modulus, primitiveRoot);
buffer.View.CopyToCPU(stream, ref gpu[0], gpu.Length); buffer.View.CopyToCPU(ref gpu[0], gpu.Length);
stream.Synchronize();
stream.Dispose();
accelerator.Dispose();
for (int i = 0; i < cpu.Length; i++) for (int i = 0; i < cpu.Length; i++)
{ {
gpu[i].Should().Be(cpu[i]); gpu[i].Should().Be(cpu[i]);
} }
}
finally
{
context.Dispose();
}
} }
[Fact] [Fact]
@@ -1,4 +1,5 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core.Gpu;
using Xunit; using Xunit;
namespace PerfectNumbers.Core.Tests.Gpu; namespace PerfectNumbers.Core.Tests.Gpu;
@@ -8,23 +9,41 @@ public class PrimeTesterGpuTests
{ {
[Fact] [Fact]
public void IsPrimeGpu_accepts_known_primes() public void IsPrimeGpu_accepts_known_primes()
{
GpuContextPool.ForceCpu = false;
try
{ {
var tester = new PrimeTester(); var tester = new PrimeTester();
ulong[] primes = [31UL, 61UL, 89UL, 107UL, 127UL, 521UL]; ulong[] primes = [31UL, 61UL, 89UL, 107UL, 127UL, 521UL];
foreach (ulong prime in primes) foreach (ulong prime in primes)
{ {
PrimeTester.IsPrimeGpu(prime).Should().BeTrue(); tester.IsPrimeGpu(prime, CancellationToken.None).Should().BeTrue();
}
}
finally
{
GpuContextPool.ForceCpu = false;
GpuContextPool.DisposeAll();
} }
} }
[Fact] [Fact]
public void IsPrimeGpu_rejects_composites() public void IsPrimeGpu_rejects_composites()
{
GpuContextPool.ForceCpu = false;
try
{ {
var tester = new PrimeTester(); var tester = new PrimeTester();
ulong[] composites = [33UL, 39UL, 51UL, 77UL, 91UL]; ulong[] composites = [33UL, 39UL, 51UL, 77UL, 91UL];
foreach (ulong composite in composites) foreach (ulong composite in composites)
{ {
PrimeTester.IsPrimeGpu(composite).Should().BeFalse(); tester.IsPrimeGpu(composite, CancellationToken.None).Should().BeFalse();
}
}
finally
{
GpuContextPool.ForceCpu = false;
GpuContextPool.DisposeAll();
} }
} }
@@ -1,4 +1,5 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using Xunit; using Xunit;
namespace PerfectNumbers.Core.Tests.Gpu; namespace PerfectNumbers.Core.Tests.Gpu;
+18 -18
View File
@@ -7,26 +7,26 @@ namespace PerfectNumbers.Core.Tests;
[Collection("GpuNtt")] [Collection("GpuNtt")]
public class GpuKernelPoolTests public class GpuKernelPoolTests
{ {
// [Fact] [Fact]
// [Trait("Category", "Fast")] [Trait("Category", "Fast")]
// public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls() public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls()
// { {
// GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true); GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
// try try
// { {
// var accelerator = lease.Accelerator; var accelerator = lease.Accelerator;
// var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
// var second = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); var second = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
// second.Equals(first).Should().BeTrue(); second.Equals(first).Should().BeTrue();
// second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1); second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1);
// } }
// finally finally
// { {
// lease.Dispose(); lease.Dispose();
// } }
// } }
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -17,31 +17,19 @@ public class GpuWorkLimiterTests
var startedSecond = new TaskCompletionSource<bool>(); var startedSecond = new TaskCompletionSource<bool>();
var sw = new Stopwatch(); var sw = new Stopwatch();
var first = GpuWorkLimiter.Acquire(); using var first = GpuWorkLimiter.Acquire();
sw.Start(); sw.Start();
var secondTask = Task.Run(() => var secondTask = Task.Run(() =>
{ {
var second = GpuWorkLimiter.Acquire(); using var second = GpuWorkLimiter.Acquire();
try
{
startedSecond.TrySetResult(true); startedSecond.TrySetResult(true);
}
finally
{
second.Dispose();
}
}); });
try
{
await Task.Delay(50); await Task.Delay(50);
startedSecond.Task.IsCompleted.Should().BeFalse(); startedSecond.Task.IsCompleted.Should().BeFalse();
}
finally
{
first.Dispose(); first.Dispose();
}
await secondTask.WaitAsync(TimeSpan.FromSeconds(1)); await secondTask.WaitAsync(TimeSpan.FromSeconds(1));
sw.Stop(); 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 candidates = new List<ulong> { 5UL, 7UL, 11UL, 13UL };
var tester = new FakeTester(); 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 compositeMarks = 0;
int clearedMarks = 0; int clearedMarks = 0;
@@ -22,7 +22,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
startPrime: 0UL, startPrime: 0UL,
markComposite: () => compositeMarks++, markComposite: () => compositeMarks++,
clearComposite: () => clearedMarks++, 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, threadCount: 1,
primesPerTask: 2); primesPerTask: 2);
@@ -32,10 +32,10 @@ public class MersenneNumberDivisorByDivisorTesterTests
results.Should().Equal( results.Should().Equal(
new[] new[]
{ {
(5UL, true, true, true, 0UL), (5UL, true, true, true),
(7UL, true, true, false, 7UL), (7UL, true, true, false),
(11UL, true, true, false, 11UL), (11UL, true, true, false),
(13UL, true, true, true, 0UL), (13UL, true, true, true),
}); });
} }
@@ -57,23 +57,10 @@ public class MersenneNumberDivisorByDivisorTesterTests
return DivisorLimit; return DivisorLimit;
} }
public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor) public bool IsPrime(ulong prime, out bool divisorsExhausted)
{ {
divisorsExhausted = true; divisorsExhausted = true;
if (prime % 7UL == 0UL) return prime % 7UL != 0UL && prime % 11UL != 0UL;
{
divisor = 7UL;
return false;
}
if (prime % 11UL == 0UL)
{
divisor = 11UL;
return false;
}
divisor = 0UL;
return true;
} }
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession() public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
@@ -96,7 +83,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
throw new NotSupportedException(); throw new NotSupportedException();
} }
public void Return() public void Dispose()
{ {
} }
} }
@@ -13,17 +13,14 @@ public class MersenneNumberDivisorCpuTesterTests
var tester = new MersenneNumberDivisorByDivisorCpuTester(); var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(11UL); 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(); 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(); 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(); divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
} }
[Fact] [Fact]
@@ -33,9 +30,7 @@ public class MersenneNumberDivisorCpuTesterTests
var tester = new MersenneNumberDivisorByDivisorCpuTester(); var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(13UL); tester.ConfigureFromMaxPrime(13UL);
var session = tester.CreateDivisorSession(); using var session = tester.CreateDivisorSession();
try
{
ulong[] primes = { 5UL, 7UL, 11UL, 13UL }; ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
byte[] hits = new byte[primes.Length]; byte[] hits = new byte[primes.Length];
@@ -48,11 +43,6 @@ public class MersenneNumberDivisorCpuTesterTests
session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes, hits); session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes, hits);
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 }); hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
} }
finally
{
session.Return();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -61,9 +51,7 @@ public class MersenneNumberDivisorCpuTesterTests
var tester = new MersenneNumberDivisorByDivisorCpuTester(); var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(43UL); tester.ConfigureFromMaxPrime(43UL);
var session = tester.CreateDivisorSession(); using var session = tester.CreateDivisorSession();
try
{
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL }; ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
byte[] hits = new byte[exponents.Length]; byte[] hits = new byte[exponents.Length];
@@ -73,11 +61,6 @@ public class MersenneNumberDivisorCpuTesterTests
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
} }
finally
{
session.Return();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -86,9 +69,7 @@ public class MersenneNumberDivisorCpuTesterTests
var tester = new MersenneNumberDivisorByDivisorCpuTester(); var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(61UL); tester.ConfigureFromMaxPrime(61UL);
var session = tester.CreateDivisorSession(); using var session = tester.CreateDivisorSession();
try
{
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL }; ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
byte[] hits = new byte[exponents.Length]; byte[] hits = new byte[exponents.Length];
@@ -98,10 +79,5 @@ public class MersenneNumberDivisorCpuTesterTests
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
} }
finally
{
session.Return();
}
}
} }
@@ -73,17 +73,14 @@ public class MersenneNumberDivisorGpuTesterTests
var tester = new MersenneNumberDivisorByDivisorGpuTester(); var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(43UL); 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(); 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(); 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(); exhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
} }
[Fact] [Fact]
@@ -108,15 +105,12 @@ public class MersenneNumberDivisorGpuTesterTests
var tester = new MersenneNumberDivisorByDivisorGpuTester(); var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(41UL); tester.ConfigureFromMaxPrime(41UL);
tester.IsPrime(31UL, out _, out ulong divisor).Should().BeTrue(); tester.IsPrime(31UL, out _).Should().BeTrue();
divisor.Should().Be(0UL); tester.IsPrime(37UL, out bool exhausted).Should().BeFalse();
tester.IsPrime(37UL, out bool exhausted, out divisor).Should().BeFalse();
exhausted.Should().BeTrue(); 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(); divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
} }
@@ -130,16 +124,8 @@ public class MersenneNumberDivisorGpuTesterTests
var tester = new MersenneNumberDivisorByDivisorGpuTester(); var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(exponent); 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(); exhausted.Should().BeTrue();
if (expectedPrime)
{
divisor.Should().Be(0UL);
}
else
{
divisor.Should().BeGreaterThan(0UL);
}
} }
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -152,9 +138,8 @@ public class MersenneNumberDivisorGpuTesterTests
tester.ConfigureFromMaxPrime(43UL); 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(); divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
} }
[Fact] [Fact]
@@ -164,9 +149,7 @@ public class MersenneNumberDivisorGpuTesterTests
var tester = new MersenneNumberDivisorByDivisorGpuTester(); var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(43UL); tester.ConfigureFromMaxPrime(43UL);
var session = tester.CreateDivisorSession(); using var session = tester.CreateDivisorSession();
try
{
ulong[] primes = { 31UL, 37UL, 41UL }; ulong[] primes = { 31UL, 37UL, 41UL };
byte[] hits = new byte[primes.Length]; byte[] hits = new byte[primes.Length];
@@ -174,11 +157,6 @@ public class MersenneNumberDivisorGpuTesterTests
hits.Should().ContainInOrder(new byte[] { 0, 1, 0 }); hits.Should().ContainInOrder(new byte[] { 0, 1, 0 });
} }
finally
{
session.Return();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -187,9 +165,7 @@ public class MersenneNumberDivisorGpuTesterTests
var tester = new MersenneNumberDivisorByDivisorGpuTester(); var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(43UL); tester.ConfigureFromMaxPrime(43UL);
var session = tester.CreateDivisorSession(); using var session = tester.CreateDivisorSession();
try
{
ulong[] primes = { 31UL, 37UL, 41UL, 43UL }; ulong[] primes = { 31UL, 37UL, 41UL, 43UL };
byte[] hits = new byte[primes.Length]; byte[] hits = new byte[primes.Length];
@@ -202,11 +178,6 @@ public class MersenneNumberDivisorGpuTesterTests
session.CheckDivisor(13367UL, MontgomeryDivisorData.FromModulus(13367UL), cycle13367, primes, hits); session.CheckDivisor(13367UL, MontgomeryDivisorData.FromModulus(13367UL), cycle13367, primes, hits);
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 }); hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
} }
finally
{
session.Return();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -219,9 +190,7 @@ public class MersenneNumberDivisorGpuTesterTests
tester.ConfigureFromMaxPrime(47UL); tester.ConfigureFromMaxPrime(47UL);
var session = tester.CreateDivisorSession(); using var session = tester.CreateDivisorSession();
try
{
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL }; ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
byte[] hits = new byte[primes.Length]; byte[] hits = new byte[primes.Length];
@@ -230,11 +199,6 @@ public class MersenneNumberDivisorGpuTesterTests
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 }); hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
} }
finally
{
session.Return();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -246,9 +210,7 @@ public class MersenneNumberDivisorGpuTesterTests
}; };
tester.ConfigureFromMaxPrime(43UL); tester.ConfigureFromMaxPrime(43UL);
var session = tester.CreateDivisorSession(); using var session = tester.CreateDivisorSession();
try
{
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL }; ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
byte[] hits = new byte[exponents.Length]; byte[] hits = new byte[exponents.Length];
@@ -259,11 +221,6 @@ public class MersenneNumberDivisorGpuTesterTests
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
} }
finally
{
session.Return();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -275,9 +232,7 @@ public class MersenneNumberDivisorGpuTesterTests
}; };
tester.ConfigureFromMaxPrime(61UL); tester.ConfigureFromMaxPrime(61UL);
var session = tester.CreateDivisorSession(); using var session = tester.CreateDivisorSession();
try
{
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL }; ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
byte[] hits = new byte[exponents.Length]; byte[] hits = new byte[exponents.Length];
@@ -288,11 +243,6 @@ public class MersenneNumberDivisorGpuTesterTests
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
} }
finally
{
session.Return();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -324,15 +274,15 @@ public class MersenneNumberDivisorGpuTesterTests
.GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)! .GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)!
.SetValue(tester, 200UL); .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(); divisorsExhausted.Should().BeTrue();
divisor.Should().Be(0UL);
} }
finally finally
{ {
tableField.SetValue(cycles, originalTable); tableField.SetValue(cycles, originalTable);
smallCyclesField.SetValue(cycles, originalSmall); smallCyclesField.SetValue(cycles, originalSmall);
DivisorCycleCache.Shared.RefreshSnapshot(); DivisorCycleCache.Shared.RefreshSnapshot();
GpuContextPool.DisposeAll();
} }
} }
@@ -347,9 +297,7 @@ public class MersenneNumberDivisorGpuTesterTests
tester.ConfigureFromMaxPrime(47UL); tester.ConfigureFromMaxPrime(47UL);
var session = tester.CreateDivisorSession(); using var session = tester.CreateDivisorSession();
try
{
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL }; ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
byte[] hits = new byte[primes.Length]; byte[] hits = new byte[primes.Length];
@@ -358,10 +306,5 @@ public class MersenneNumberDivisorGpuTesterTests
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 }); hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
} }
finally
{
session.Return();
}
}
} }
@@ -1,4 +1,5 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Cpu; using PerfectNumbers.Core.Cpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -1,4 +1,5 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -1,4 +1,5 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Cpu; using PerfectNumbers.Core.Cpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -1,5 +1,6 @@
using System.Reflection; using System.Reflection;
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -54,6 +55,7 @@ public class MersenneNumberOrderGpuTesterTests
ulong[]? backupSmall = originalSmall is null ? null : (ulong[])originalSmall.Clone(); ulong[]? backupSmall = originalSmall is null ? null : (ulong[])originalSmall.Clone();
cycles.LoadFrom(tempPath); cycles.LoadFrom(tempPath);
GpuContextPool.DisposeAll();
try try
{ {
@@ -73,6 +75,7 @@ public class MersenneNumberOrderGpuTesterTests
{ {
tableField.SetValue(cycles, originalTable); tableField.SetValue(cycles, originalTable);
smallCyclesField.SetValue(cycles, backupSmall); smallCyclesField.SetValue(cycles, backupSmall);
GpuContextPool.DisposeAll();
} }
} }
finally finally
@@ -1,4 +1,5 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Cpu; using PerfectNumbers.Core.Cpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -1,4 +1,5 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -17,29 +17,12 @@ public class PrimeTesterIsPrimeTests
[InlineData(97UL, true)] [InlineData(97UL, true)]
[InlineData(133UL, false)] [InlineData(133UL, false)]
[InlineData(137UL, true)] [InlineData(137UL, true)]
[InlineData(143UL, false)]
[InlineData(199UL, true)]
[InlineData(209UL, false)]
[InlineData(403UL, false)]
[InlineData(341UL, false)] [InlineData(341UL, false)]
public void IsPrime_returns_expected_results(ulong n, bool expected) public void IsPrime_returns_expected_results(ulong n, bool expected)
{ {
var tester = new PrimeTester(); var tester = new PrimeTester();
PrimeTester.IsPrime(n).Should().Be(expected); tester.IsPrime(n, CancellationToken.None).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);
} }
[Fact] [Fact]
@@ -47,14 +30,20 @@ public class PrimeTesterIsPrimeTests
public void IsPrimeGpu_falls_back_to_cpu_when_forced() public void IsPrimeGpu_falls_back_to_cpu_when_forced()
{ {
var tester = new PrimeTester(); var tester = new PrimeTester();
PrimeTester.IsPrime(137UL).Should().BeTrue(); GpuContextPool.ForceCpu = true;
PrimeTester.IsPrime(341UL).Should().BeFalse();
tester.IsPrimeGpu(137UL, CancellationToken.None).Should().BeTrue();
tester.IsPrimeGpu(341UL, CancellationToken.None).Should().BeFalse();
GpuContextPool.ForceCpu = false;
} }
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
public void IsPrimeGpu_handles_parallel_calls_for_known_primes() public void IsPrimeGpu_handles_parallel_calls_for_known_primes()
{ {
GpuContextPool.ForceCpu = false;
GpuPrimeWorkLimiter.SetLimit(64); GpuPrimeWorkLimiter.SetLimit(64);
PrimeTester.GpuBatchSize = 256_000; PrimeTester.GpuBatchSize = 256_000;
@@ -64,7 +53,7 @@ public class PrimeTesterIsPrimeTests
var testers = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true); var testers = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true);
Parallel.For(0, primes.Length, i => 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); results.Should().AllBeEquivalentTo(true);
@@ -73,7 +62,7 @@ public class PrimeTesterIsPrimeTests
{ {
foreach (ulong prime in primes) 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 lastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne;
var lastSeven = PrimesGenerator.SmallPrimesLastSeven; var lastSeven = PrimesGenerator.SmallPrimesLastSeven;
var lastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven; 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); lastOne.Length.Should().Be((int)PerfectNumberConstants.PrimesLimit);
lastSeven.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); lastOnePow2.Length.Should().Be(lastOne.Length);
lastSevenPow2.Length.Should().Be(lastSeven.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]); lastOne[..5].Should().Equal([3U, 7U, 11U, 13U, 19U]);
lastSeven[..5].Should().Equal([3U, 7U, 11U, 13U, 17U]); 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++) for (int i = 0; i < lastOne.Length; i++)
{ {
@@ -83,64 +58,6 @@ public class PrimesGeneratorTests
.Should().BeTrue(); .Should().BeTrue();
lastSevenPow2[i].Should().Be(prime * (ulong)prime); 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 internal sealed class MersenneCpuDivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
{ {
private bool _disposed;
public void Reset() public void Reset()
{ {
_disposed = false;
} }
public void CheckDivisor( 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); ThreadStaticPools.ReturnMersenneCpuDivisorSession(this);
} }
} }
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Cpu; namespace PerfectNumbers.Core.Cpu;
@@ -42,7 +43,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit); public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit);
public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor) public bool IsPrime(ulong prime, out bool divisorsExhausted)
{ {
ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit); ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
@@ -60,8 +61,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
bool composite = CheckDivisors( bool composite = CheckDivisors(
prime, prime,
allowedMax, allowedMax,
out processedAll, out processedAll);
out divisor);
if (composite) if (composite)
{ {
@@ -70,7 +70,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
} }
divisorsExhausted = processedAll || composite; divisorsExhausted = processedAll || composite;
divisor = 0UL;
return true; return true;
} }
@@ -98,10 +97,10 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
private bool CheckDivisors( private bool CheckDivisors(
ulong prime, ulong prime,
ulong allowedMax, ulong allowedMax,
out bool processedAll, out bool processedAll)
out ulong foundDivisor)
{ {
foundDivisor = 0UL; processedAll = false;
// The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs. // 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. // Keeping the guard commented out documents the reasoning for benchmarks and tests.
// if (allowedMax < 3UL) // if (allowedMax < 3UL)
@@ -127,91 +126,49 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
if (divisor.CompareTo(limit) > 0) if (divisor.CompareTo(limit) > 0)
{ {
processedAll = true; processedAll = true;
foundDivisor = 0UL;
return false; return false;
} }
// Intentionally recomputes factorizations without a per-thread cache. // Intentionally recomputes factorizations without a per-thread cache.
// The previous factor cache recorded virtually no hits and only slowed down the scan. // The previous factor cache recorded virtually no hits and only slowed down the scan.
_ = DivisorCycleCache.Shared; DivisorCycleCache cycleCache = DivisorCycleCache.Shared;
ulong stepHigh = step.High; ulong stepHigh = step.High;
ulong stepLow = step.Low; ulong stepLow = step.Low;
ulong limitHigh = limit.High; 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; 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 // Keep the divisibility filters aligned with the divisor-cycle generator so the
// CPU path never requests cycles that were skipped during cache creation. // CPU path never requests cycles that were skipped during cache creation.
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit); 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) 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; 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); MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(candidate);
ulong divisorCycle; 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. // Divisors generated from 2 * k * p + 1 exceed the small-cycle snapshot when p >= 138,000,000, so the short path below never runs.
@@ -222,7 +179,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
// else // else
{ {
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu( if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
gpu,
candidate, candidate,
prime, prime,
divisorData, divisorData,
@@ -246,9 +202,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
{ {
// A cycle equal to the tested exponent (which is prime in this path) guarantees that the candidate divides // 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. // the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p.
foundDivisor = candidate;
processedAll = true; processedAll = true;
PrimeOrderCalculatorAccelerator.Return(gpu);
return true; return true;
} }
@@ -259,154 +213,18 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
} }
divisor.Add(step); divisor.Add(step);
remainder10 = AddMod10(remainder10, step10); remainder10 = AddMod(remainder10, step10, (byte)10);
remainder8 = AddMod8(remainder8, step8); remainder8 = AddMod8(remainder8, step8);
remainder3 = AddMod3(remainder3, step3); remainder5 = AddMod(remainder5, step5, (byte)5);
remainder7 = AddMod7(remainder7, step7); remainder3 = AddMod(remainder3, step3, (byte)3);
remainder11 = AddMod11(remainder11, step11); remainder7 = AddMod(remainder7, step7, (byte)7);
remainder11 = AddMod(remainder11, step11, (byte)11);
} }
PrimeOrderCalculatorAccelerator.Return(gpu); processedAll = divisor.CompareTo(limit) > 0;
processedAll = true;
foundDivisor = 0UL;
return false; 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) private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
{ {
ulong residue = prime.Pow2MontgomeryModWithCycleCpu(divisorCycle, divisorData); ulong residue = prime.Pow2MontgomeryModWithCycleCpu(divisorCycle, divisorData);
@@ -414,57 +232,13 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod3(byte value, byte delta) private static byte AddMod(byte value, byte delta, byte modulus)
{ {
const int Modulus = 3;
int sum = value + delta; int sum = value + delta;
if (sum >= Modulus) if (sum >= modulus)
{ {
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; return (byte)sum;
@@ -1,6 +1,6 @@
namespace PerfectNumbers.Core.Cpu; namespace PerfectNumbers.Core;
internal static class DeterministicRandomCpu internal static class DeterministicRandom
{ {
[ThreadStatic] [ThreadStatic]
private static ulong s_threadState; private static ulong s_threadState;
+48 -94
View File
@@ -1,8 +1,11 @@
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core; namespace PerfectNumbers.Core;
@@ -11,9 +14,8 @@ public sealed class DivisorCycleCache
private const byte ByteZero = 0; private const byte ByteZero = 0;
private const byte ByteOne = 1; private const byte ByteOne = 1;
private const int StackBufferThreshold = 128; private const int StackBufferThreshold = 128;
private readonly Accelerator _accelerator;
private readonly int _acceleratorIndex; 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 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 ulong[] _snapshot;
private volatile bool _useGpuGeneration = true; private volatile bool _useGpuGeneration = true;
private readonly int _divisorCyclesBatchSize; private readonly int _divisorCyclesBatchSize;
@@ -34,16 +36,9 @@ public sealed class DivisorCycleCache
public int PreferredBatchSize => _divisorCyclesBatchSize; public int PreferredBatchSize => _divisorCyclesBatchSize;
// private static readonly Accelerator[] _accelerators;
private DivisorCycleCache(int divisorCyclesBatchSize) private DivisorCycleCache(int divisorCyclesBatchSize)
{ {
_divisorCyclesBatchSize = Math.Max(1, 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(); _snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
} }
@@ -84,6 +79,7 @@ public sealed class DivisorCycleCache
throw new InvalidDataException($"Divisor cycle is missing for {divisor}"); throw new InvalidDataException($"Divisor cycle is missing for {divisor}");
} }
// ObserveCycleCacheDivisor(divisor);
return cached; return cached;
} }
@@ -98,7 +94,7 @@ public sealed class DivisorCycleCache
int[]? rentedMissing = null; int[]? rentedMissing = null;
Span<int> missingBuffer = length <= StackBufferThreshold Span<int> missingBuffer = length <= StackBufferThreshold
? stackalloc int[length] ? stackalloc int[length]
: new Span<int>(rentedMissing = ThreadStaticPools.IntPool.Rent(length), 0, length); : new Span<int>(rentedMissing = ArrayPool<int>.Shared.Rent(length), 0, length);
int missingCount = 0; int missingCount = 0;
@@ -118,6 +114,7 @@ public sealed class DivisorCycleCache
throw new InvalidDataException($"Divisor cycle is missing for {divisor}"); throw new InvalidDataException($"Divisor cycle is missing for {divisor}");
} }
// ObserveCycleCacheDivisor(divisor);
cycles[i] = cached; cycles[i] = cached;
} }
else else
@@ -141,7 +138,7 @@ public sealed class DivisorCycleCache
if (rentedMissing is not null) if (rentedMissing is not null)
{ {
ThreadStaticPools.IntPool.Return(rentedMissing, clearArray: false); ArrayPool<int>.Shared.Return(rentedMissing, clearArray: false);
} }
} }
@@ -200,12 +197,12 @@ public sealed class DivisorCycleCache
ulong[]? rentedDivisors = null; ulong[]? rentedDivisors = null;
Span<ulong> workDivisors = count <= StackBufferThreshold Span<ulong> workDivisors = count <= StackBufferThreshold
? stackalloc ulong[count] ? stackalloc ulong[count]
: new Span<ulong>(rentedDivisors = ThreadStaticPools.UlongPool.Rent(count), 0, count); : new Span<ulong>(rentedDivisors = ArrayPool<ulong>.Shared.Rent(count), 0, count);
ulong[]? rentedResults = null; ulong[]? rentedResults = null;
Span<ulong> workResults = count <= StackBufferThreshold Span<ulong> workResults = count <= StackBufferThreshold
? stackalloc ulong[count] ? stackalloc ulong[count]
: new Span<ulong>(rentedResults = ThreadStaticPools.UlongPool.Rent(count), 0, count); : new Span<ulong>(rentedResults = ArrayPool<ulong>.Shared.Rent(count), 0, count);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
@@ -221,80 +218,48 @@ public sealed class DivisorCycleCache
if (rentedDivisors is not null) if (rentedDivisors is not null)
{ {
ThreadStaticPools.UlongPool.Return(rentedDivisors, clearArray: false); ArrayPool<ulong>.Shared.Return(rentedDivisors, clearArray: false);
ThreadStaticPools.UlongPool.Return(rentedResults!, clearArray: false); ArrayPool<ulong>.Shared.Return(rentedResults!, clearArray: false);
}
}
private const int DefaultCapacity = 128;
[ThreadStatic]
private static MemoryBuffer1D<ulong, Stride1D.Dense>? divisorBuffer;
[ThreadStatic]
private static MemoryBuffer1D<ulong, Stride1D.Dense>? powBuffer;
[ThreadStatic]
private static MemoryBuffer1D<ulong, Stride1D.Dense>? orderBuffer;
[ThreadStatic]
private static MemoryBuffer1D<ulong, Stride1D.Dense>? resultBuffer;
[ThreadStatic]
private static MemoryBuffer1D<byte, Stride1D.Dense>? statusBuffer;
private void EnsureCapacity(int requiredCapacity)
{
if (requiredCapacity <= (divisorBuffer?.Length ?? 0))
{
return;
}
if (divisorBuffer != null)
{
divisorBuffer.Dispose();
powBuffer!.Dispose();
orderBuffer!.Dispose();
resultBuffer!.Dispose();
statusBuffer!.Dispose();
}
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);
} }
} }
private void ComputeCyclesGpuCore(ReadOnlySpan<ulong> divisors, Span<ulong> destination) private void ComputeCyclesGpuCore(ReadOnlySpan<ulong> divisors, Span<ulong> destination)
{ {
int length = divisors.Length; int length = divisors.Length;
var gpuLease = GpuKernelPool.GetKernel(useGpuOrder: true);
var execution = gpuLease.EnterExecutionScope();
EnsureCapacity(length); Accelerator accelerator = gpuLease.Accelerator;
var kernel = _gpuKernelCache.GetOrAdd(accelerator, LoadKernel);
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);
ulong[]? rentedPow = null; ulong[]? rentedPow = null;
Span<ulong> powSpan = length <= StackBufferThreshold Span<ulong> powSpan = length <= StackBufferThreshold
? stackalloc ulong[length] ? stackalloc ulong[length]
: new Span<ulong>(rentedPow = ThreadStaticPools.UlongPool.Rent(length), 0, length); : new Span<ulong>(rentedPow = ArrayPool<ulong>.Shared.Rent(length), 0, length);
ulong[]? rentedOrder = null; ulong[]? rentedOrder = null;
Span<ulong> orderSpan = length <= StackBufferThreshold Span<ulong> orderSpan = length <= StackBufferThreshold
? stackalloc ulong[length] ? stackalloc ulong[length]
: new Span<ulong>(rentedOrder = ThreadStaticPools.UlongPool.Rent(length), 0, length); : new Span<ulong>(rentedOrder = ArrayPool<ulong>.Shared.Rent(length), 0, length);
ulong[]? rentedResult = null; ulong[]? rentedResult = null;
Span<ulong> resultSpan = length <= StackBufferThreshold Span<ulong> resultSpan = length <= StackBufferThreshold
? stackalloc ulong[length] ? stackalloc ulong[length]
: new Span<ulong>(rentedResult = ThreadStaticPools.UlongPool.Rent(length), 0, length); : new Span<ulong>(rentedResult = ArrayPool<ulong>.Shared.Rent(length), 0, length);
byte[]? rentedStatus = null; byte[]? rentedStatus = null;
Span<byte> statusSpan = length <= StackBufferThreshold Span<byte> statusSpan = length <= StackBufferThreshold
? stackalloc byte[length] ? stackalloc byte[length]
: new Span<byte>(rentedStatus = ThreadStaticPools.BytePool.Rent(length), 0, length); : new Span<byte>(rentedStatus = ArrayPool<byte>.Shared.Rent(length), 0, length);
ref ulong divisorRef = ref MemoryMarshal.GetReference(divisors);
divisorBuffer.View.CopyFromCPU(ref divisorRef, length);
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
@@ -311,25 +276,18 @@ public sealed class DivisorCycleCache
statusSpan[i] = ByteZero; statusSpan[i] = ByteZero;
} }
// GpuPrimeWorkLimiter.Acquire(); 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);
int pending; int pending;
var kernel = _gpuKernel;
var acceleratorIndex = _acceleratorIndex;
var stream = AcceleratorStreamPool.Rent(acceleratorIndex);
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 do
{ {
kernel(stream, length, _divisorCyclesBatchSize, divisorBuffer.View, powBuffer.View, orderBuffer.View, resultBuffer.View, statusBuffer.View); kernel(length, _divisorCyclesBatchSize, divisorBuffer.View, powBuffer.View, orderBuffer.View, resultBuffer.View, statusBuffer.View);
accelerator.Synchronize();
statusBuffer.View.CopyToCPU(stream, statusSpan);
stream.Synchronize();
statusBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(statusSpan), length);
pending = 0; pending = 0;
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
@@ -341,27 +299,23 @@ public sealed class DivisorCycleCache
} }
while (pending > 0); while (pending > 0);
resultBuffer.View.CopyToCPU(stream, resultSpan); resultBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(resultSpan), length);
stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
resultSpan.CopyTo(destination); resultSpan.CopyTo(destination);
if (rentedPow is not null) if (rentedPow is not null)
{ {
ThreadStaticPools.UlongPool.Return(rentedPow, clearArray: false); ArrayPool<ulong>.Shared.Return(rentedPow, clearArray: false);
ThreadStaticPools.UlongPool.Return(rentedOrder!, clearArray: false); ArrayPool<ulong>.Shared.Return(rentedOrder!, clearArray: false);
ThreadStaticPools.UlongPool.Return(rentedResult!, clearArray: false); ArrayPool<ulong>.Shared.Return(rentedResult!, clearArray: false);
ThreadStaticPools.BytePool.Return(rentedStatus!, clearArray: false); ArrayPool<byte>.Shared.Return(rentedStatus!, clearArray: false);
} }
// GpuPrimeWorkLimiter.Release(); execution.Dispose();
gpuLease.Dispose();
} }
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) 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)
{ {
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); 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);
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> private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
+16 -248
View File
@@ -4,272 +4,40 @@ namespace PerfectNumbers.Core;
internal static class DivisorGenerator 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 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); 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort GetDecimalMask(LastDigit lastDigit) public static ushort GetDecimalMask(LastDigit lastDigit)
{ {
return lastDigit switch return lastDigit == LastDigit.Seven ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise;
{
LastDigit.Seven => DecimalMaskWhenLastIsSeven,
LastDigit.Three => DecimalMaskWhenLastIsThree,
LastDigit.Nine => DecimalMaskWhenLastIsNine,
_ => 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)
{
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)
{
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;
}
lastOneWithoutThree = lastOneWithoutThreeList.ToArray();
lastOneWithoutThreePow2 = lastOneWithoutThreePow2List.ToArray();
lastSevenWithoutThree = lastSevenWithoutThreeList.ToArray();
lastSevenWithoutThreePow2 = lastSevenWithoutThreePow2List.ToArray();
lastNineWithoutThree = lastNineWithoutThreeList.ToArray();
lastNineWithoutThreePow2 = lastNineWithoutThreePow2List.ToArray();
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static LastDigit GetLastDigit(uint prime) public static bool IsValidDivisor(
byte remainder10,
byte remainder8,
byte remainder3,
byte remainder5,
byte remainder7,
byte remainder11,
ushort decimalMask)
{ {
return (prime % 10U) switch if (((decimalMask >> remainder10) & 1) == 0)
{ {
1U => LastDigit.One, return false;
3U => LastDigit.Three,
5U => LastDigit.Five,
7U => LastDigit.Seven,
9U => LastDigit.Nine,
_ => throw new ArgumentOutOfRangeException(nameof(prime)),
};
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] if (remainder8 != 1 && remainder8 != 7)
private static bool IsAllowedForLastOne(uint prime, LastDigit lastDigit)
{ {
// TODO: Swap the `% 10` usage for ULongExtensions.Mod10 so the hot classification path return false;
// reuses the benchmarked residue helper instead of repeated divisions.
return lastDigit switch
{
LastDigit.One or LastDigit.Three or LastDigit.Nine => true,
LastDigit.Seven => prime == 7U,
_ => prime == 11U,
};
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] if (remainder3 == 0 || remainder5 == 0 || remainder7 == 0 || remainder11 == 0)
private static bool IsAllowedForLastSeven(uint prime, LastDigit lastDigit)
{ {
// TODO: Route this `% 10` classification through ULongExtensions.Mod10 to match the faster return false;
// residue helper used elsewhere in the scanner.
return lastDigit switch
{
LastDigit.Three or LastDigit.Seven or LastDigit.Nine => true,
_ => prime == 11U,
};
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] return true;
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,
};
} }
} }
@@ -74,7 +74,7 @@ internal struct ExponentRemainderStepperCpu
// single-block divisor-cycle snapshot so we can skip the powmod entirely and reuse the // single-block divisor-cycle snapshot so we can skip the powmod entirely and reuse the
// cached Montgomery residue ladder highlighted in MersenneDivisorCycleLengthGpuBenchmarks. // cached Montgomery residue ladder highlighted in MersenneDivisorCycleLengthGpuBenchmarks.
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true); ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
_currentMontgomery = _currentMontgomery.MontgomeryMultiplyCpu(multiplier, _modulus, _nPrime); _currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
PreviousExponent = exponent; PreviousExponent = exponent;
return ReduceCurrent(); return ReduceCurrent();
} }
@@ -92,7 +92,7 @@ internal struct ExponentRemainderStepperCpu
// TODO: Reuse the divisor-cycle derived Montgomery delta once the cache exposes single-cycle // 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. // lookups so this branch also avoids recomputing powmods when the snapshot lacks the divisor.
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true); ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
_currentMontgomery = _currentMontgomery.MontgomeryMultiplyCpu(multiplier, _modulus, _nPrime); _currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
PreviousExponent = exponent; PreviousExponent = exponent;
return _currentMontgomery == _montgomeryOne; 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 // 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 // of relying on the caller-provided delta so incremental scans remain in sync with the snapshot
// without computing additional cycles or mutating cache state. // without computing additional cycles or mutating cache state.
_currentMontgomery = _currentMontgomery.MontgomeryMultiplyCpu(montgomeryDelta, _modulus, _nPrime); _currentMontgomery = _currentMontgomery.MontgomeryMultiply(montgomeryDelta, _modulus, _nPrime);
PreviousExponent = exponent; PreviousExponent = exponent;
isUnity = _currentMontgomery == _montgomeryOne; isUnity = _currentMontgomery == _montgomeryOne;
return true; return true;
@@ -138,5 +138,5 @@ internal struct ExponentRemainderStepperCpu
PreviousExponent = exponent; 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 System.Runtime.CompilerServices;
using PerfectNumbers.Core;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -24,7 +25,10 @@ internal struct ExponentRemainderStepperGpu
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool MatchesDivisor(in GpuDivisorPartialData divisor) => _modulus == divisor.Modulus; public bool MatchesDivisor(in GpuDivisorPartialData divisor)
{
return _modulus == divisor.Modulus;
}
/// <summary> /// <summary>
/// Clears the cached residue so the stepper can be reused for another divisor. /// Clears the cached residue so the stepper can be reused for another divisor.
+163
View File
@@ -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;
}
}
}
+213
View File
@@ -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);
}
}
}
}
+253 -180
View File
@@ -1,188 +1,188 @@
using System.Collections.Concurrent;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu.Accelerators; using static PerfectNumbers.Core.Gpu.GpuContextPool;
namespace PerfectNumbers.Core.Gpu; 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 public class GpuKernelPool
{ {
[ThreadStatic] 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.
private static KernelContainer[]? _pool;
public static T InitOnce<T>(ref T? target, Func<T> valueFactory) where T : class private static KernelContainer GetKernels(Accelerator accelerator)
{ {
if (target is { } value) return KernelCache.GetOrAdd(accelerator, _ => new KernelContainer());
{
return value;
} }
var newValue = valueFactory();
return Interlocked.CompareExchange(ref target, newValue, null) ?? newValue;
}
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
internal static KernelContainer GetOrAddKernels(int acceleratorIndex, AcceleratorStream stream, KernelType kernelType)
{
var pool = _pool ??= [];
if (pool[acceleratorIndex] is not { } kernels)
{
kernels = new();
}
Accelerator accelerator = _accelerators[acceleratorIndex];
if (kernelType.HasFlag(KernelType.OrderKernelScan))
{
InitOrderKernelScan(accelerator, kernels);
}
if (kernelType.HasFlag(KernelType.Pow2ModKernelScan))
{
InitPow2ModKernelScan(accelerator, kernels);
}
if (kernelType.HasFlag(KernelType.IncrementalKernelScan))
{
InitIncrementalKernelScan(accelerator, kernels);
}
if (kernelType.HasFlag(KernelType.IncrementalOrderKernelScan))
{
IncrementalOrderKernelScan(accelerator, kernels);
}
if (kernelType.HasFlag(KernelType.Pow2ModOrderKernelScan))
{
InitPow2ModOrderKernelScan(accelerator, kernels);
}
PreloadStaticTables(accelerator, kernels, stream, kernelType);
pool[acceleratorIndex] = kernels;
return kernels;
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 kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>>();
});
}
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);
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>>>();
});
}
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);
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>>>();
});
}
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);
var kernel = KernelUtil.GetKernel(loaded);
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. // 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). // Returns the ArrayView to pass into kernels (when kernels are extended to accept it).
private static void EnsureSmallCyclesOnDevice(Accelerator accelerator, KernelContainer kernels, AcceleratorStream stream) public static ArrayView1D<ulong, Stride1D.Dense> EnsureSmallCyclesOnDevice(Accelerator accelerator)
{ {
if (kernels.SmallCycles is { }) var kernels = GetKernels(accelerator);
if (kernels.SmallCycles is { } buffer)
{ {
return; return buffer.View;
} }
var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); // Ensure single upload per accelerator even if multiple threads race here.
MemoryBuffer1D<ulong, Stride1D.Dense>? device; 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.
lock (accelerator)
{ {
device = accelerator.Allocate1D<ulong>(host.Length); if (kernels.SmallCycles is { } existing)
{
return existing.View;
} }
device.View.CopyFromCPU(stream, host); 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; kernels.SmallCycles = device;
return device.View;
}
} }
public static ResiduePrimeViews GetSmallPrimesOnDevice(KernelContainer kernels) => new( public static ResiduePrimeViews EnsureSmallPrimesOnDevice(Accelerator accelerator)
kernels.SmallPrimesLastOne!,
kernels.SmallPrimesLastSeven!,
kernels.SmallPrimesPow2LastOne!,
kernels.SmallPrimesPow2LastSeven!
);
private static void EnsureSmallPrimesOnDevice(Accelerator accelerator, KernelContainer kernels, AcceleratorStream stream)
{ {
if (kernels.SmallPrimesLastOne is { } && var kernels = GetKernels(accelerator);
kernels.SmallPrimesLastSeven is { } && if (kernels.SmallPrimesLastOne is { } lastOne &&
kernels.SmallPrimesPow2LastOne is { } && kernels.SmallPrimesLastSeven is { } lastSeven &&
kernels.SmallPrimesPow2LastSeven is { }) kernels.SmallPrimesPow2LastOne is { } lastOnePow2 &&
kernels.SmallPrimesPow2LastSeven is { } lastSevenPow2)
{ {
return; return new ResiduePrimeViews(lastOne.View, lastSeven.View, lastOnePow2.View, lastSevenPow2.View);
} }
if (kernels.SmallPrimesLastOne is { } && 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.
kernels.SmallPrimesLastSeven is { } &&
kernels.SmallPrimesPow2LastOne is { } &&
kernels.SmallPrimesPow2LastSeven is { })
{ {
return; 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);
} }
var hostLastOne = PrimesGenerator.SmallPrimesLastOne; var hostLastOne = PrimesGenerator.SmallPrimesLastOne;
@@ -190,27 +190,100 @@ public class GpuKernelPool
var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne; var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne;
var hostLastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven; var hostLastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven;
MemoryBuffer1D<uint, Stride1D.Dense>? deviceLastOne; var deviceLastOne = accelerator.Allocate1D<uint>(hostLastOne.Length);
MemoryBuffer1D<uint, Stride1D.Dense>? deviceLastSeven; deviceLastOne.View.CopyFromCPU(hostLastOne);
MemoryBuffer1D<ulong, Stride1D.Dense>? deviceLastOnePow2; var deviceLastSeven = accelerator.Allocate1D<uint>(hostLastSeven.Length);
MemoryBuffer1D<ulong, Stride1D.Dense>? deviceLastSevenPow2; 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);
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.SmallPrimesLastOne = deviceLastOne;
kernels.SmallPrimesLastSeven = deviceLastSeven; kernels.SmallPrimesLastSeven = deviceLastSeven;
kernels.SmallPrimesPow2LastOne = deviceLastOnePow2; kernels.SmallPrimesPow2LastOne = deviceLastOnePow2;
kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2; kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2;
return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View);
}
}
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);
}
lock (kernels)
{
if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares)
{
return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.Length);
}
var hostPrimes = PrimesGenerator.SmallPrimes;
var hostSquares = PrimesGenerator.SmallPrimesPow2;
var devicePrimes = accelerator.Allocate1D<uint>(hostPrimes.Length);
devicePrimes.View.CopyFromCPU(hostPrimes);
var deviceSquares = accelerator.Allocate1D<ulong>(hostSquares.Length);
deviceSquares.View.CopyFromCPU(hostSquares);
kernels.SmallPrimeFactorsPrimes = devicePrimes;
kernels.SmallPrimeFactorsSquares = deviceSquares;
return new SmallPrimeFactorTables(devicePrimes, deviceSquares, hostPrimes.Length);
}
}
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;
}
MemoryBuffer1D<int, Stride1D.Dense>? exponentSlots = kernels.SmallPrimeFactorExponentSlots;
if (exponentSlots is null || exponentSlots.Length < slotCount)
{
exponentSlots?.Dispose();
exponentSlots = accelerator.Allocate1D<int>(slotCount);
kernels.SmallPrimeFactorExponentSlots = exponentSlots;
}
MemoryBuffer1D<int, Stride1D.Dense>? countSlot = kernels.SmallPrimeFactorCountSlot;
if (countSlot is null)
{
countSlot = accelerator.Allocate1D<int>(1);
kernels.SmallPrimeFactorCountSlot = countSlot;
}
MemoryBuffer1D<ulong, Stride1D.Dense>? remainingSlot = kernels.SmallPrimeFactorRemainingSlot;
if (remainingSlot is null)
{
remainingSlot = accelerator.Allocate1D<ulong>(1);
kernels.SmallPrimeFactorRemainingSlot = remainingSlot;
}
return new SmallPrimeFactorScratch(primeSlots!, exponentSlots!, countSlot!, remainingSlot!);
}
}
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);
} }
/// <summary> /// <summary>
@@ -219,14 +292,14 @@ public class GpuKernelPool
/// <param name="action">Action to run with (Accelerator, Stream).</param> /// <param name="action">Action to run with (Accelerator, Stream).</param>
public static void Run(Action<Accelerator, AcceleratorStream> action) public static void Run(Action<Accelerator, AcceleratorStream> action)
{ {
// GpuPrimeWorkLimiter.Acquire(); var lease = GetKernel(useGpuOrder: true);
var acceleratorIndex = AcceleratorPool.Shared.Rent(); var execution = lease.EnterExecutionScope();
var accelerator = _accelerators[acceleratorIndex]; var accelerator = lease.Accelerator;
var stream = AcceleratorStreamPool.Rent(acceleratorIndex); var stream = accelerator.CreateStream();
action(accelerator, stream); action(accelerator, stream);
stream.Synchronize();
stream.Dispose(); stream.Dispose();
accelerator.Dispose(); execution.Dispose();
// GpuPrimeWorkLimiter.Release(); lease.Dispose();
} }
} }
+36 -5
View File
@@ -3,18 +3,27 @@ namespace PerfectNumbers.Core.Gpu;
public static class GpuPrimeWorkLimiter public static class GpuPrimeWorkLimiter
{ {
private static SemaphoreSlim _semaphore = new(1, int.MaxValue); private static SemaphoreSlim _semaphore = new(1, int.MaxValue);
private static readonly object _lock = new();
private static int _currentLimit = 1; private static int _currentLimit = 1;
public static void Acquire() { } // => _semaphore.Wait(); public static IDisposable Acquire()
public static void Release() { }// => _semaphore.Release(); {
// 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) public static void SetLimit(int value)
{ {
if (value <= 0) if (value < 1)
{ {
value = 1; value = 1;
} }
lock (_lock)
{
if (value == _currentLimit) if (value == _currentLimit)
{ {
return; return;
@@ -22,9 +31,31 @@ public static class GpuPrimeWorkLimiter
// TODO: Switch to a shared limiter implementation with GpuWorkLimiter so we can coordinate CPU/GPU prime work using // 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. // the same semaphore pool without reallocating per adjustment.
var previous = _semaphore;
_semaphore = new SemaphoreSlim(value, value); _semaphore = new SemaphoreSlim(value, value);
_currentLimit = value; _currentLimit = value;
previous.Dispose(); }
}
private sealed class Releaser : IDisposable
{
private readonly SemaphoreSlim _sem;
private bool _disposed;
public Releaser(SemaphoreSlim sem)
{
_sem = sem;
}
public void Dispose()
{
if (_disposed)
{
return;
}
_sem.Release();
_disposed = true;
}
} }
} }
+17 -47
View File
@@ -261,13 +261,13 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
public override readonly int GetHashCode() => HashCode.Combine(High, Low); public override readonly int GetHashCode() => HashCode.Combine(High, Low);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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)] [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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(ulong otherHigh, ulong otherLow) private void AddInternal(ulong otherHigh, ulong otherLow)
{ {
ulong originalLow = Low; ulong originalLow = Low;
// We're reusing originalLow for carry to avoid an extra local. // We're reusing originalLow for carry to avoid an extra local.
@@ -316,7 +316,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
return; return;
} }
Add(valueHigh, valueLow); AddInternal(valueHigh, valueLow);
if (CompareTo(in modulus) >= 0) if (CompareTo(in modulus) >= 0)
{ {
Sub(in modulus); Sub(in modulus);
@@ -589,6 +589,9 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GpuUInt128 BinaryGcd(GpuUInt128 u, GpuUInt128 v) 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) if (u.IsZero)
{ {
return v; return v;
@@ -599,50 +602,26 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
return u; return u;
} }
ulong combinedLow = u.Low | v.Low; int shift = TrailingZeroCount(new GpuUInt128(u.High | v.High, 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 zu = TrailingZeroCount(u); int zu = TrailingZeroCount(u);
u >>= zu; u >>= zu;
GpuUInt128 currentV = v; do
while (true)
{ {
int zv = TrailingZeroCount(currentV); int zv = TrailingZeroCount(v);
currentV >>= zv; v >>= zv;
if (u > v)
bool swap = u > currentV;
GpuUInt128 min = ConditionalSelect(u, currentV, swap);
GpuUInt128 max = ConditionalSelect(currentV, u, swap);
max.Sub(min);
if (max.IsZero)
{ {
return min << shift; (u, v) = (v, u);
} }
u = min; v -= u;
currentV = max;
} }
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) public void Sub(ulong value)
{ {
@@ -1550,15 +1529,6 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
high = hi; 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. // TODO: Check if the TODO below is still relevant.
+13 -6
View File
@@ -6,14 +6,14 @@ public static class GpuWorkLimiter
private static readonly object _lock = new(); private static readonly object _lock = new();
private static int _currentLimit = 1; 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 // GpuLimiterThroughputBenchmarks so limiter acquisition does not allocate when we enter the
// GPU scanning hot path. // GPU scanning hot path.
var sem = _semaphore; var sem = _semaphore;
sem.Wait(); sem.Wait();
return new Lease(sem); return new Releaser(sem);
} }
public static void SetLimit(int value) 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 // Swap to a new semaphore for the new limit. We intentionally
// do not dispose the old semaphore to avoid releasing handles // 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 // TODO: Consolidate with GpuPrimeWorkLimiter so both limiters share a pooled SemaphoreSlim and avoid rebuilding the
// limiter state whenever limits are adjusted from the CLI. // limiter state whenever limits are adjusted from the CLI.
_semaphore = new SemaphoreSlim(value, value); _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 readonly SemaphoreSlim _sem;
private bool _disposed;
internal Lease(SemaphoreSlim sem) public Releaser(SemaphoreSlim sem)
{ {
_sem = sem; _sem = sem;
} }
public void Dispose() public void Dispose()
{ {
if (_disposed)
{
return;
}
_sem.Release(); _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();
}
}
-14
View File
@@ -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;
using ILGPU.Algorithms;
using ILGPU.Runtime;
using PerfectNumbers.Core;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -50,28 +53,15 @@ internal static class DivisorByDivisorKernels
int endIndex = offset + count; int endIndex = offset + count;
if (hasCycle) if (hasCycle)
{ {
GpuUInt128 remainderValue = new(cycleRemainder);
for (int exponentIndex = offset + 1; exponentIndex < endIndex; exponentIndex++) for (int exponentIndex = offset + 1; exponentIndex < endIndex; exponentIndex++)
{ {
ulong exponent = exponents[exponentIndex]; ulong exponent = exponents[exponentIndex];
ulong delta = exponent - previousExponent; ulong delta = exponent - previousExponent;
previousExponent = exponent; previousExponent = exponent;
if (delta >= cycleLength) remainderValue.AddMod(delta, cycleLength);
{ cycleRemainder = remainderValue.Low;
delta -= cycleLength;
if (delta >= cycleLength)
{
delta %= cycleLength;
}
}
ulong nextRemainder = cycleRemainder + delta;
if (nextRemainder >= cycleLength)
{
nextRemainder -= cycleLength;
}
cycleRemainder = nextRemainder;
byte hit = 0; byte hit = 0;
if (cycleRemainder == 0UL) if (cycleRemainder == 0UL)
@@ -1,5 +1,6 @@
using ILGPU; using ILGPU;
using ILGPU.Algorithms; using ILGPU.Algorithms;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -1,4 +1,5 @@
using ILGPU; using ILGPU;
using ILGPU.Algorithms;
using ILGPU.Runtime; using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -1,5 +1,7 @@
using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Runtime;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -303,7 +305,7 @@ internal static class LucasLehmerKernels
states[idx] = s; 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). // Lucas–Lehmer iteration in the field GF(2^p-1).
// For p < 128, use a Mersenne-specific squaring + reduction to avoid // For p < 128, use a Mersenne-specific squaring + reduction to avoid
@@ -1,6 +1,5 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -33,7 +32,7 @@ internal static class NttBarrettKernels
} }
// Barrett-based squaring: v = v^2 mod n for 128-bit n // 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]; 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); 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 System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -44,7 +43,7 @@ internal static class NttMontgomeryKernels
data[index] = new GpuUInt128(0UL, v); 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 aR = data[index].Low;
ulong v = MontMul64(aR, aR, modulus, nPrime); ulong v = MontMul64(aR, aR, modulus, nPrime);
@@ -1,4 +1,5 @@
using ILGPU; using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -1,4 +1,5 @@
using ILGPU; using ILGPU;
using ILGPU.Algorithms;
using ILGPU.Runtime; using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -1,26 +1,18 @@
using System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core;
namespace PerfectNumbers.Core.Gpu.Kernels; namespace PerfectNumbers.Core.Gpu;
internal static class Pow2MontgomeryKernels 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( public static void Pow2MontgomeryKernelKeepMontgomery(
Index1D index, Index1D index,
ArrayView1D<ulong, Stride1D.Dense> exponents, ArrayView1D<ulong, Stride1D.Dense> exponents,
MontgomeryDivisorData divisor, MontgomeryDivisorData divisor,
ArrayView1D<ulong, Stride1D.Dense> results) ArrayView1D<ulong, Stride1D.Dense> results)
{ {
results[0] = ULongExtensions.Pow2MontgomeryModWindowedGpuKeepMontgomery(divisor, exponents[0]); results[index] = ULongExtensions.Pow2MontgomeryModWindowedGpuKeepMontgomery(divisor, exponents[index]);
} }
public static void Pow2MontgomeryKernelConvertToStandard( public static void Pow2MontgomeryKernelConvertToStandard(
@@ -29,52 +21,6 @@ internal static class Pow2MontgomeryKernels
MontgomeryDivisorData divisor, MontgomeryDivisorData divisor,
ArrayView1D<ulong, Stride1D.Dense> results) ArrayView1D<ulong, Stride1D.Dense> results)
{ {
results[0] = ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisor, exponents[0]); results[index] = ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisor, exponents[index]);
}
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;
} }
} }
@@ -1,51 +1,15 @@
using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Algorithms.Random;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
internal static partial class PrimeOrderGpuHeuristics internal static partial class PrimeOrderGpuHeuristics
{ {
internal static void CheckFactorsKernel(
int count,
ulong phi,
ArrayView1D<KeyValuePair<ulong, int>, Stride1D.Dense> toTest,
MontgomeryDivisorData divisorData,
ArrayView1D<ulong, Stride1D.Dense> result
)
{
ulong order = phi;
int entryCount = count;
for (int i = 0; i < entryCount; i++)
{
(ulong primeFactor, int exponent) = toTest[i];
for (int iteration = 0; iteration < exponent; iteration++)
{
ulong candidate = order / primeFactor;
ulong remainder = order - (candidate * primeFactor);
// ulong remainder = order % primeFactor;
if (remainder != 0UL)
{
break;
}
// ulong candidate = order / primeFactor;
if (ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisorData, candidate) == 1UL)
{
order = candidate;
continue;
}
break;
}
}
result[0] = order;
}
/// This kernel always sets the result and operates within the allowed / required bounds, never accessing elements outside of them. /// This kernel always sets the result and operates within the allowed / required bounds, never accessing elements outside of them.
/// It sets the value only when it needs to. The callers don't need to clear the output / input buffers. /// It sets the value only when it needs to. The callers don't need to clear the output / input buffers.
internal static void PartialFactorKernel( internal static void PartialFactorKernel(
@@ -1206,6 +1170,16 @@ internal static partial class PrimeOrderGpuHeuristics
remainders[index] = ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisor, exponent); remainders[index] = ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisor, exponent);
} }
private static Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>> GetPow2ModWideKernel(Accelerator accelerator)
{
return Pow2ModKernelWideCache.GetOrAdd(accelerator, static accel =>
{
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>>(Pow2ModKernelWide);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>>>();
});
}
/// This kernel always sets the result of the corresponding element. Callers don't need to clear the output buffers. /// This kernel always sets the result of the corresponding element. Callers don't need to clear the output buffers.
internal static void Pow2ModKernelWide(Index1D index, ArrayView1D<GpuUInt128, Stride1D.Dense> exponents, GpuUInt128 modulus, ArrayView1D<GpuUInt128, Stride1D.Dense> remainders) internal static void Pow2ModKernelWide(Index1D index, ArrayView1D<GpuUInt128, Stride1D.Dense> exponents, GpuUInt128 modulus, ArrayView1D<GpuUInt128, Stride1D.Dense> remainders)
{ {
@@ -1393,4 +1367,5 @@ internal static partial class PrimeOrderGpuHeuristics
ulong directMask = (1UL << windowBitCount) - 1UL; ulong directMask = (1UL << windowBitCount) - 1UL;
return exponent.Low & directMask; return exponent.Low & directMask;
} }
} }
@@ -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 System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Algorithms; using ILGPU.Algorithms;
using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
public enum HeuristicGpuDivisorTableKind : byte
{
GroupA = 0,
GroupBEnding1 = 1,
GroupBEnding7 = 7,
GroupBEnding9 = 9,
}
internal static class PrimeTesterKernels internal static class PrimeTesterKernels
{ {
// GPU kernel: small-prime sieve only. Returns 1 if passes sieve (probable prime), 0 otherwise. // GPU kernel: small-prime sieve only. Returns 1 if passes sieve (probable prime), 0 otherwise.
public static void SmallPrimeSieveKernel( public static void SmallPrimeSieveKernel(Index1D index, ArrayView<ulong> numbers, ArrayView<uint> smallPrimes, ArrayView<byte> results)
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)
{ {
ulong n = numbers[index]; ulong n = numbers[index];
byte result = 1; byte result = 1;
ArrayView<uint> primes; // EvenPerfectBitScanner filters candidates so production GPU launches only see:
ArrayView<ulong> primeSquares; // - n >= 31 (the scanner starts at 136,279,841 and test mode seeds 31)
ulong lastDigit = n % 10UL; // - n odd (candidate generators and ModResidueTracker eliminate even exponents)
switch (lastDigit) // - 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.
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;
}
int length = (int)primes.Length; long length = smallPrimes.Length;
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
ulong primeSquare = primeSquares[i]; ulong prime = smallPrimes[i];
ulong primeSquare = (ulong)prime * prime;
if (primeSquare > n) if (primeSquare > n)
{ {
break; break;
} }
ulong prime = primes[i];
if (n % prime == 0UL) if (n % prime == 0UL)
{ {
result = 0; result = 0;
@@ -78,69 +38,16 @@ internal static class PrimeTesterKernels
results[index] = result; results[index] = result;
} }
public static void HeuristicTrialCombinedDivisionKernel( public static void HeuristicTrialDivisionKernel(Index1D index, ArrayView<ulong> divisors, ulong n, ArrayView<byte> results)
Index1D index,
byte nMod10,
ArrayView<int> resultFlag,
ulong n,
ulong maxDivisorSquare,
HeuristicCombinedGpuViews tables)
{ {
// byte nMod10 = (byte)(n % 10UL); ulong divisor = divisors[index];
ArrayView1D<ulong, Stride1D.Dense> divisors = tables.SelectDivisors(nMod10); if (divisor <= 1UL)
ArrayView1D<ulong, Stride1D.Dense> divisorSquares = tables.SelectDivisorSquares(nMod10);
int divisorLength = (int)divisorSquares.Length;
int threadIndex = index;
if (threadIndex >= divisorLength)
{ {
results[index] = 0;
return; return;
} }
ulong divisorSquare = divisorSquares[threadIndex]; results[index] = n % divisor == 0UL ? (byte)1 : (byte)0;
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;
}
} }
public static void SharesFactorKernel(Index1D index, ArrayView<ulong> numbers, ArrayView<byte> results) 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) 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 uIsZero = u == 0UL;
bool vIsZero = v == 0UL; bool vIsZero = v == 0UL;
bool eitherZero = uIsZero | vIsZero; bool eitherZero = uIsZero | vIsZero;
ulong zeroResult = uIsZero ? v : u; ulong zeroResult = uIsZero ? v : u;
ulong combined = (u | v) | 1UL; ulong combined = u | v | 1UL;
int shift = XMath.TrailingZeroCount(combined); 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); currentV >>= XMath.TrailingZeroCount(currentV);
bool swap = currentU > currentV; ulong minValue = XMath.Min(currentU, currentV);
ulong minValue = ConditionalSelect(currentU, currentV, swap); ulong maxValue = XMath.Max(currentU, currentV);
ulong maxValue = ConditionalSelect(currentV, currentU, swap);
currentV = maxValue - minValue;
currentU = minValue; currentU = minValue;
if (currentV == 0UL) currentV = maxValue - minValue;
{ loopCondition = currentV != 0UL;
}
ulong gcd = currentU << shift; ulong gcd = currentU << shift;
return eitherZero ? zeroResult : gcd; return eitherZero ? zeroResult : gcd;
} }
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong ConditionalSelect(ulong left, ulong right, bool useRight)
{
ulong mask = useRight ? ulong.MaxValue : 0UL;
return left ^ ((left ^ right) & mask);
}
} }
@@ -1,10 +1,12 @@
using System;
using System.Buffers; using System.Buffers;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using PerfectNumbers.Core;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -21,26 +23,15 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
private ulong _divisorLimit; private ulong _divisorLimit;
// private bool _isConfigured; // private bool _isConfigured;
[ThreadStatic] private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>> _kernelCache = new(AcceleratorReferenceComparer.Instance);
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>>? _kernel; private readonly ConcurrentBag<BatchResources> _resourcePool = new();
private readonly ConcurrentBag<GpuContextPool.GpuContextLease> _acceleratorPool = new();
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
private readonly ConcurrentBag<BatchResources> _resourcePool = []; private Action<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> GetKernel(Accelerator accelerator) =>
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = []; _kernelCache.GetOrAdd(accelerator, acc =>
acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>(DivisorByDivisorKernels.CheckKernel));
private static Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> GetKernel(Accelerator accelerator)
{
var pool = _kernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>(DivisorByDivisorKernels.CheckKernel);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>>();
pool[accelerator] = cached;
return cached;
}
public int GpuBatchSize public int GpuBatchSize
{ {
@@ -68,9 +59,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
// _isConfigured = true; // _isConfigured = true;
} }
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; public bool IsPrime(ulong prime, out bool divisorsExhausted)
public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor)
{ {
ulong allowedMax; ulong allowedMax;
int batchCapacity; int batchCapacity;
@@ -102,25 +91,18 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ulong processedCount; ulong processedCount;
ulong lastProcessed; ulong lastProcessed;
// GpuPrimeWorkLimiter(); var gpuLease = RentAccelerator();
// int acceleratorIndex = AcceleratorPool.Shared.Rent(); var accelerator = gpuLease.Accelerator;
// var accelerator = _accelerators[acceleratorIndex];
var gpu = PrimeOrderCalculatorAccelerator.Rent(batchCapacity);
var acceleratorIndex = gpu.AcceleratorIndex;
var accelerator = gpu.Accelerator;
var stream = AcceleratorStreamPool.Rent(acceleratorIndex);
// Monitor.Enter(gpuLease.ExecutionLock); Monitor.Enter(gpuLease.ExecutionLock);
var kernel = GetKernel(accelerator); var kernel = GetKernel(accelerator);
var resources = RentBatchResources(accelerator, batchCapacity); var resources = RentBatchResources(accelerator, batchCapacity);
composite = CheckDivisors( composite = CheckDivisors(
gpu,
prime, prime,
allowedMax, allowedMax,
kernel, kernel,
stream,
resources.DivisorDataBuffer, resources.DivisorDataBuffer,
resources.OffsetBuffer, resources.OffsetBuffer,
resources.CountBuffer, resources.CountBuffer,
@@ -130,6 +112,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
resources.HitIndexBuffer, resources.HitIndexBuffer,
resources.Divisors, resources.Divisors,
resources.Exponents, resources.Exponents,
resources.FilteredDivisors,
resources.DivisorData, resources.DivisorData,
resources.Offsets, resources.Offsets,
resources.Counts, resources.Counts,
@@ -138,21 +121,17 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
out coveredRange, out coveredRange,
out processedCount); out processedCount);
AcceleratorStreamPool.Return(acceleratorIndex, stream);
ReturnBatchResources(resources); ReturnBatchResources(resources);
PrimeOrderCalculatorAccelerator.Return(gpu); Monitor.Exit(gpuLease.ExecutionLock);
// Monitor.Exit(gpuLease.ExecutionLock); ReturnAccelerator(gpuLease);
// GpuPrimeWorkLimiter.Release();
if (composite) if (composite)
{ {
divisorsExhausted = true; divisorsExhausted = true;
divisor = lastProcessed;
return false; return false;
} }
divisorsExhausted = coveredRange; divisorsExhausted = coveredRange;
divisor = 0UL;
return true; return true;
} }
@@ -186,11 +165,9 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
private static bool CheckDivisors( private static bool CheckDivisors(
PrimeOrderCalculatorAccelerator gpu,
ulong prime, ulong prime,
ulong allowedMax, ulong allowedMax,
Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> kernel, Action<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> kernel,
AcceleratorStream stream,
MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense> divisorDataBuffer, MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense> divisorDataBuffer,
MemoryBuffer1D<int, Stride1D.Dense> offsetBuffer, MemoryBuffer1D<int, Stride1D.Dense> offsetBuffer,
MemoryBuffer1D<int, Stride1D.Dense> countBuffer, MemoryBuffer1D<int, Stride1D.Dense> countBuffer,
@@ -200,6 +177,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
MemoryBuffer1D<int, Stride1D.Dense> hitIndexBuffer, MemoryBuffer1D<int, Stride1D.Dense> hitIndexBuffer,
ulong[] divisors, ulong[] divisors,
ulong[] exponents, ulong[] exponents,
ulong[] filteredDivisors,
GpuDivisorPartialData[] divisorData, GpuDivisorPartialData[] divisorData,
int[] offsets, int[] offsets,
int[] counts, int[] counts,
@@ -210,7 +188,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
{ {
int batchCapacity = (int)divisorDataBuffer.Length; int batchCapacity = (int)divisorDataBuffer.Length;
bool composite = false; bool composite = false;
bool processedAll; bool processedAll = false;
processedCount = 0UL; processedCount = 0UL;
lastProcessed = 0UL; lastProcessed = 0UL;
@@ -241,23 +219,24 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128; ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128;
ulong remainingCount = maxK; ulong remainingCount = maxK;
ulong stepValue = (ulong)twoP128; byte step10 = (byte)(twoP128 % 10UL);
byte step10 = (byte)(stepValue % 10UL); byte step8 = (byte)(twoP128 % 8UL);
byte step8 = (byte)(stepValue & 7UL); byte step5 = (byte)(twoP128 % 5UL);
byte step3 = (byte)(stepValue % 3UL); byte step3 = (byte)(twoP128 % 3UL);
byte step7 = (byte)(stepValue % 7UL); byte step7 = (byte)(twoP128 % 7UL);
byte step11 = (byte)(stepValue % 11UL); byte step11 = (byte)(twoP128 % 11UL);
UInt128 currentDivisor128 = firstDivisor128; UInt128 currentDivisor128 = firstDivisor128;
ulong currentDivisorValue = (ulong)currentDivisor128; byte remainder10 = (byte)((ulong)(currentDivisor128 % 10UL));
byte remainder10 = (byte)(currentDivisorValue % 10UL); byte remainder8 = (byte)((ulong)(currentDivisor128 & 7UL));
byte remainder8 = (byte)(currentDivisorValue & 7UL); byte remainder5 = (byte)((ulong)(currentDivisor128 % 5UL));
byte remainder3 = (byte)(currentDivisorValue % 3UL); byte remainder3 = (byte)((ulong)(currentDivisor128 % 3UL));
byte remainder7 = (byte)(currentDivisorValue % 7UL); byte remainder7 = (byte)((ulong)(currentDivisor128 % 7UL));
byte remainder11 = (byte)(currentDivisorValue % 11UL); byte remainder11 = (byte)((ulong)(currentDivisor128 % 11UL));
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit); ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit);
Span<ulong> filteredStorage = filteredDivisors.AsSpan();
Span<ulong> divisorStorage = divisors.AsSpan(); Span<ulong> divisorStorage = divisors.AsSpan();
Span<ulong> exponentStorage = exponents.AsSpan(); Span<ulong> exponentStorage = exponents.AsSpan();
Span<GpuDivisorPartialData> divisorDataStorage = divisorData.AsSpan(); Span<GpuDivisorPartialData> divisorDataStorage = divisorData.AsSpan();
@@ -292,50 +271,37 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
chunkCount = (int)remainingCount; chunkCount = (int)remainingCount;
} }
int filteredCount = 0;
UInt128 nextDivisor128 = currentDivisor128; UInt128 nextDivisor128 = currentDivisor128;
int admissibleCount = 0;
byte localRemainder10 = remainder10; byte localRemainder10 = remainder10;
byte localRemainder8 = remainder8; byte localRemainder8 = remainder8;
byte localRemainder5 = remainder5;
byte localRemainder3 = remainder3; byte localRemainder3 = remainder3;
byte localRemainder7 = remainder7; byte localRemainder7 = remainder7;
byte localRemainder11 = remainder11; byte localRemainder11 = remainder11;
for (int i = 0; i < chunkCount; i++) for (int i = 0; i < chunkCount; i++)
{ {
bool passesSmallModuli = localRemainder3 != 0 && localRemainder7 != 0 && localRemainder11 != 0; ulong candidate = (ulong)nextDivisor128;
if (passesSmallModuli && (localRemainder8 == 1 || localRemainder8 == 7) && ((decimalMask >> localRemainder10) & 1) != 0)
{
ulong divisorValue = (ulong)nextDivisor128;
MontgomeryDivisorData montgomeryData = MontgomeryDivisorData.FromModulus(divisorValue);
ulong divisorCycle = ResolveDivisorCycle(gpu, divisorValue, prime, in montgomeryData);
if (divisorCycle == prime)
{
processedCount += (ulong)(i + 1);
lastProcessed = divisorValue;
coveredRange = true;
return true;
}
divisorSpan[admissibleCount] = divisorValue; if (DivisorGenerator.IsValidDivisor(localRemainder10, localRemainder8, localRemainder3, localRemainder5, localRemainder7, localRemainder11, decimalMask))
divisorDataSpan[admissibleCount] = new GpuDivisorPartialData(divisorValue); {
offsetSpan[admissibleCount] = admissibleCount; filteredStorage[filteredCount++] = candidate;
countSpan[admissibleCount] = 1;
cycleSpan[admissibleCount] = divisorCycle;
exponentSpan[admissibleCount] = prime;
admissibleCount++;
} }
nextDivisor128 += twoP128; nextDivisor128 += twoP128;
localRemainder10 = AddMod10(localRemainder10, step10); localRemainder10 = AddMod(localRemainder10, step10, 10);
localRemainder8 = AddMod8(localRemainder8, step8); localRemainder8 = AddMod8(localRemainder8, step8);
localRemainder3 = AddMod3(localRemainder3, step3); localRemainder5 = AddMod(localRemainder5, step5, 5);
localRemainder7 = AddMod7(localRemainder7, step7); localRemainder3 = AddMod(localRemainder3, step3, 3);
localRemainder11 = AddMod11(localRemainder11, step11); localRemainder7 = AddMod(localRemainder7, step7, 7);
localRemainder11 = AddMod(localRemainder11, step11, 11);
} }
remainder10 = localRemainder10; remainder10 = localRemainder10;
remainder8 = localRemainder8; remainder8 = localRemainder8;
remainder5 = localRemainder5;
remainder3 = localRemainder3; remainder3 = localRemainder3;
remainder7 = localRemainder7; remainder7 = localRemainder7;
remainder11 = localRemainder11; remainder11 = localRemainder11;
@@ -347,25 +313,50 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
currentDivisor128 = nextDivisor128; currentDivisor128 = nextDivisor128;
remainingCount -= (ulong)chunkCount; remainingCount -= (ulong)chunkCount;
if (filteredCount == 0)
{
continue;
}
int admissibleCount = 0;
for (int i = 0; i < filteredCount; i++)
{
ulong divisorValue = filteredStorage[i];
MontgomeryDivisorData montgomeryData = MontgomeryDivisorData.FromModulus(divisorValue);
ulong divisorCycle = ResolveDivisorCycle(divisorValue, prime, in montgomeryData);
if (divisorCycle != prime)
{
continue;
}
divisorSpan[admissibleCount] = divisorValue;
exponentSpan[admissibleCount] = prime;
divisorDataSpan[admissibleCount] = new GpuDivisorPartialData(divisorValue);
offsetSpan[admissibleCount] = admissibleCount;
countSpan[admissibleCount] = 1;
cycleSpan[admissibleCount] = divisorCycle;
admissibleCount++;
}
if (admissibleCount == 0) if (admissibleCount == 0)
{ {
continue; continue;
} }
divisorDataView.CopyFromCPU(stream, ref divisorDataRef, admissibleCount); divisorDataView.CopyFromCPU(ref divisorDataRef, admissibleCount);
offsetView.CopyFromCPU(stream, ref offsetRef, admissibleCount); offsetView.CopyFromCPU(ref offsetRef, admissibleCount);
countView.CopyFromCPU(stream, ref countRef, admissibleCount); countView.CopyFromCPU(ref countRef, admissibleCount);
cycleView.CopyFromCPU(stream, ref cycleRef, admissibleCount); cycleView.CopyFromCPU(ref cycleRef, admissibleCount);
exponentViewDevice.CopyFromCPU(stream, ref exponentRef, admissibleCount); exponentViewDevice.CopyFromCPU(ref exponentRef, admissibleCount);
int sentinel = int.MaxValue; int sentinel = int.MaxValue;
hitIndexView.CopyFromCPU(stream, ref sentinel, 1); hitIndexView.CopyFromCPU(ref sentinel, 1);
kernel(stream, new Index1D(admissibleCount), divisorDataView, offsetView, countView, exponentViewDevice, cycleView, hitsView, hitIndexView); kernel(admissibleCount, divisorDataView, offsetView, countView, exponentViewDevice, cycleView, hitsView, hitIndexView);
int firstHit = sentinel; int firstHit = sentinel;
hitIndexView.CopyToCPU(stream, ref firstHit, 1); hitIndexView.CopyToCPU(ref firstHit, 1);
stream.Synchronize();
int hitIndex = firstHit >= admissibleCount ? -1 : firstHit; int hitIndex = firstHit >= admissibleCount ? -1 : firstHit;
bool hitFound = hitIndex >= 0; bool hitFound = hitIndex >= 0;
composite = hitFound; composite = hitFound;
@@ -380,9 +371,9 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong ResolveDivisorCycle(PrimeOrderCalculatorAccelerator gpu, ulong divisor, ulong prime, in MontgomeryDivisorData divisorData) private static ulong ResolveDivisorCycle(ulong divisor, ulong prime, in MontgomeryDivisorData divisorData)
{ {
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(gpu, divisor, prime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL) if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(divisor, prime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL)
{ {
return MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); return MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
} }
@@ -391,62 +382,19 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod3(byte value, byte delta) private static byte AddMod(byte value, byte delta, byte modulus)
{ {
const int Modulus = 3; int result = value + delta;
int sum = value + delta;
if (sum >= Modulus) if (result >= modulus)
{ {
sum -= Modulus; result -= modulus;
} }
return (byte)sum; return (byte)result;
} }
[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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod8(byte value, byte delta) private static byte AddMod8(byte value, byte delta)
{ {
@@ -454,6 +402,9 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
private static ulong ComputeDivisorLimitFromMaxPrimeGpu(ulong maxPrime) private static ulong ComputeDivisorLimitFromMaxPrimeGpu(ulong maxPrime)
{ {
// The --mersenne=bydivisor flow in EvenPerfectBitScanner only calls ConfigureFromMaxPrime with primes greater than 1, // The --mersenne=bydivisor flow in EvenPerfectBitScanner only calls ConfigureFromMaxPrime with primes greater than 1,
@@ -477,34 +428,21 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
// { // {
// return 0UL; // return 0UL;
// } // }
ulong cappedLimit = divisorLimit;
const ulong TestPrimeDivisorCap = 170_000_000UL;
if (prime < 10_000_000UL && cappedLimit > TestPrimeDivisorCap)
{
cappedLimit = TestPrimeDivisorCap;
}
if (prime - 1UL >= 64UL) if (prime - 1UL >= 64UL)
{ {
return cappedLimit; return divisorLimit;
} }
ulong computedLimit = (1UL << (int)(prime - 1UL)) - 1UL; ulong computedLimit = (1UL << (int)(prime - 1UL)) - 1UL;
if (prime < 10_000_000UL && computedLimit > cappedLimit) return computedLimit < divisorLimit ? computedLimit : divisorLimit;
{
computedLimit = cappedLimit;
}
return computedLimit < cappedLimit ? computedLimit : cappedLimit;
} }
public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
{ {
private readonly MersenneNumberDivisorByDivisorGpuTester _owner; private readonly MersenneNumberDivisorByDivisorGpuTester _owner;
private readonly PrimeOrderCalculatorAccelerator _primeTesterAccelerator; private readonly GpuContextPool.GpuContextLease _lease;
private readonly Accelerator _accelerator; private readonly Accelerator _accelerator;
private readonly AcceleratorStream _stream; private Action<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> _kernel = null!;
private Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> _kernel = null!;
private MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense> _divisorBuffer = null!; private MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense> _divisorBuffer = null!;
private MemoryBuffer1D<int, Stride1D.Dense> _offsetBuffer = null!; private MemoryBuffer1D<int, Stride1D.Dense> _offsetBuffer = null!;
private MemoryBuffer1D<int, Stride1D.Dense> _countBuffer = null!; private MemoryBuffer1D<int, Stride1D.Dense> _countBuffer = null!;
@@ -514,42 +452,35 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
private MemoryBuffer1D<byte, Stride1D.Dense> _hitBuffer = null!; private MemoryBuffer1D<byte, Stride1D.Dense> _hitBuffer = null!;
private ulong[] _hostBuffer = null!; private ulong[] _hostBuffer = null!;
private int _capacity; private int _capacity;
private bool _disposed;
internal DivisorScanSession(MersenneNumberDivisorByDivisorGpuTester owner) internal DivisorScanSession(MersenneNumberDivisorByDivisorGpuTester owner)
{ {
_owner = owner; _owner = owner;
// GpuPrimeWorkLimiter.Acquire(); _lease = GpuContextPool.RentPreferred(preferCpu: false);
_primeTesterAccelerator = PrimeOrderCalculatorAccelerator.Rent(1); _accelerator = _lease.Accelerator;
Accelerator accelerator = _primeTesterAccelerator.Accelerator;
_accelerator = accelerator;
_stream = accelerator.CreateStream();
_capacity = Math.Max(1, owner._gpuBatchSize); _capacity = Math.Max(1, owner._gpuBatchSize);
} }
internal void Reset() internal void Reset()
{ {
_disposed = false;
} }
private void EnsureExecutionResourcesLocked(int requiredCapacity) private void EnsureExecutionResourcesLocked(int requiredCapacity)
{ {
Accelerator accelerator = _accelerator;
if (_kernel == null) if (_kernel == null)
{ {
_kernel = GetKernel(_accelerator); _kernel = _owner.GetKernel(_accelerator);
} }
if (_divisorBuffer == null) if (_divisorBuffer == null)
{ {
lock(accelerator) _divisorBuffer = _accelerator.Allocate1D<GpuDivisorPartialData>(1);
{ _offsetBuffer = _accelerator.Allocate1D<int>(1);
_divisorBuffer = accelerator.Allocate1D<GpuDivisorPartialData>(1); _countBuffer = _accelerator.Allocate1D<int>(1);
_offsetBuffer = accelerator.Allocate1D<int>(1); _cycleBuffer = _accelerator.Allocate1D<ulong>(1);
_countBuffer = accelerator.Allocate1D<int>(1); _firstHitBuffer = _accelerator.Allocate1D<int>(1);
_cycleBuffer = accelerator.Allocate1D<ulong>(1);
_firstHitBuffer = accelerator.Allocate1D<int>(1);
}
} }
bool allocateBuffers = _exponentsBuffer == null; bool allocateBuffers = _exponentsBuffer == null;
@@ -572,18 +503,19 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
{ {
int desiredCapacity = requiredCapacity > _capacity ? requiredCapacity : _capacity; int desiredCapacity = requiredCapacity > _capacity ? requiredCapacity : _capacity;
_capacity = desiredCapacity; _capacity = desiredCapacity;
lock (accelerator) _exponentsBuffer = _accelerator.Allocate1D<ulong>(_capacity);
{ _hitBuffer = _accelerator.Allocate1D<byte>(_capacity);
_exponentsBuffer = accelerator.Allocate1D<ulong>(desiredCapacity); _hostBuffer = ThreadStaticPools.UlongPool.Rent(_capacity);
_hitBuffer = accelerator.Allocate1D<byte>(desiredCapacity);
}
_hostBuffer = ThreadStaticPools.UlongPool.Rent(desiredCapacity);
} }
} }
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits) public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits)
{ {
if (_disposed)
{
throw new ObjectDisposedException(nameof(DivisorScanSession));
}
int length = primes.Length; int length = primes.Length;
// EvenPerfectBitScanner always supplies at least one exponent per divisor check, so the guard stays commented out. // EvenPerfectBitScanner always supplies at least one exponent per divisor check, so the guard stays commented out.
// if (length == 0) // if (length == 0)
@@ -604,8 +536,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
if (cycle == 0UL) if (cycle == 0UL)
{ {
var gpu = _primeTesterAccelerator; if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(divisor, firstPrime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL)
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(gpu, divisor, firstPrime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL)
{ {
cycle = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); cycle = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
} }
@@ -620,7 +551,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
} }
// Monitor.Enter(_lease.ExecutionLock); Monitor.Enter(_lease.ExecutionLock);
EnsureExecutionResourcesLocked(length); EnsureExecutionResourcesLocked(length);
@@ -632,42 +563,51 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ArrayView1D<ulong, Stride1D.Dense> exponentView = _exponentsBuffer.View; ArrayView1D<ulong, Stride1D.Dense> exponentView = _exponentsBuffer.View;
ArrayView1D<byte, Stride1D.Dense> hitView = _hitBuffer.View; ArrayView1D<byte, Stride1D.Dense> hitView = _hitBuffer.View;
AcceleratorStream stream = _stream;
GpuDivisorPartialData partialData = new GpuDivisorPartialData(divisor); GpuDivisorPartialData partialData = new GpuDivisorPartialData(divisor);
divisorView.CopyFromCPU(stream, ref partialData, 1); divisorView.CopyFromCPU(ref partialData, 1);
int offsetValue = 0; int offsetValue = 0;
offsetView.CopyFromCPU(stream, ref offsetValue, 1); offsetView.CopyFromCPU(ref offsetValue, 1);
int countValue = length; int countValue = length;
countView.CopyFromCPU(stream, ref countValue, 1); countView.CopyFromCPU(ref countValue, 1);
ulong cycleValue = cycle; ulong cycleValue = cycle;
cycleView.CopyFromCPU(stream, ref cycleValue, 1); cycleView.CopyFromCPU(ref cycleValue, 1);
Span<ulong> hostSpan = _hostBuffer.AsSpan(0, length); Span<ulong> hostSpan = _hostBuffer.AsSpan(0, length);
primes.CopyTo(hostSpan); primes.CopyTo(hostSpan);
ref ulong hostRef = ref MemoryMarshal.GetReference(hostSpan); ref ulong hostRef = ref MemoryMarshal.GetReference(hostSpan);
exponentView.CopyFromCPU(stream, ref hostRef, length); exponentView.CopyFromCPU(ref hostRef, length);
int sentinel = int.MaxValue; int sentinel = int.MaxValue;
firstHitView.CopyFromCPU(stream, ref sentinel, 1); firstHitView.CopyFromCPU(ref sentinel, 1);
var kernel = _kernel!; var kernel = _kernel;
if (kernel is null)
{
Monitor.Exit(_lease.ExecutionLock);
throw new InvalidOperationException("GPU divisor kernel was not initialized.");
}
kernel(stream, new Index1D(1), divisorView, offsetView, countView, exponentView, cycleView, hitView, firstHitView); kernel(1, divisorView, offsetView, countView, exponentView, cycleView, hitView, firstHitView);
Span<byte> hitSlice = hits.Slice(0, length); Span<byte> hitSlice = hits.Slice(0, length);
ref byte hitRef = ref MemoryMarshal.GetReference(hitSlice); ref byte hitRef = ref MemoryMarshal.GetReference(hitSlice);
hitView.CopyToCPU(stream, ref hitRef, length); hitView.CopyToCPU(ref hitRef, length);
stream.Synchronize();
// Monitor.Exit(_lease.ExecutionLock); Monitor.Exit(_lease.ExecutionLock);
} }
public void Return() public void Dispose()
{ {
if (_disposed)
{
return;
}
_disposed = true;
_owner._sessionPool.Add(this); _owner._sessionPool.Add(this);
} }
} }
@@ -692,7 +632,17 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
} }
private GpuContextPool.GpuContextLease RentAccelerator()
{
if (_acceleratorPool.TryTake(out var lease))
{
return lease;
}
return GpuContextPool.RentPreferred(preferCpu: false);
}
private void ReturnAccelerator(GpuContextPool.GpuContextLease lease) => _acceleratorPool.Add(lease);
private BatchResources RentBatchResources(Accelerator accelerator, int capacity) private BatchResources RentBatchResources(Accelerator accelerator, int capacity)
{ {
@@ -707,7 +657,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
private void ReturnBatchResources(BatchResources resources) => _resourcePool.Add(resources); private void ReturnBatchResources(BatchResources resources) => _resourcePool.Add(resources);
private sealed class BatchResources private sealed class BatchResources : IDisposable
{ {
private Accelerator? _accelerator; private Accelerator? _accelerator;
private MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense>? _divisorDataBuffer; private MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense>? _divisorDataBuffer;
@@ -744,6 +694,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
internal ulong[] Exponents { get; private set; } = null!; internal ulong[] Exponents { get; private set; } = null!;
internal ulong[] FilteredDivisors { get; private set; } = null!;
internal GpuDivisorPartialData[] DivisorData { get; private set; } = null!; internal GpuDivisorPartialData[] DivisorData { get; private set; } = null!;
internal int[] Offsets { get; private set; } = null!; internal int[] Offsets { get; private set; } = null!;
@@ -799,8 +751,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
private void AllocateDeviceBuffers(Accelerator accelerator, int capacity) private void AllocateDeviceBuffers(Accelerator accelerator, int capacity)
{
lock (accelerator)
{ {
_divisorDataBuffer = accelerator.Allocate1D<GpuDivisorPartialData>(capacity); _divisorDataBuffer = accelerator.Allocate1D<GpuDivisorPartialData>(capacity);
_offsetBuffer = accelerator.Allocate1D<int>(capacity); _offsetBuffer = accelerator.Allocate1D<int>(capacity);
@@ -809,8 +759,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
_exponentBuffer = accelerator.Allocate1D<ulong>(capacity); _exponentBuffer = accelerator.Allocate1D<ulong>(capacity);
_hitsBuffer = accelerator.Allocate1D<byte>(capacity); _hitsBuffer = accelerator.Allocate1D<byte>(capacity);
_hitIndexBuffer = accelerator.Allocate1D<int>(1); _hitIndexBuffer = accelerator.Allocate1D<int>(1);
}
_deviceCapacity = capacity; _deviceCapacity = capacity;
} }
@@ -822,6 +770,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
Divisors = ulongPool.Rent(capacity); Divisors = ulongPool.Rent(capacity);
Exponents = ulongPool.Rent(capacity); Exponents = ulongPool.Rent(capacity);
FilteredDivisors = ulongPool.Rent(capacity);
DivisorData = partialPool.Rent(capacity); DivisorData = partialPool.Rent(capacity);
Offsets = intPool.Rent(capacity); Offsets = intPool.Rent(capacity);
Counts = intPool.Rent(capacity); Counts = intPool.Rent(capacity);
@@ -860,12 +809,14 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ArrayPool<GpuDivisorPartialData> partialPool = ThreadStaticPools.GpuDivisorPartialDataPool; ArrayPool<GpuDivisorPartialData> partialPool = ThreadStaticPools.GpuDivisorPartialDataPool;
ulongPool.Return(Divisors, clearArray: false); ulongPool.Return(Divisors, clearArray: false);
ulongPool.Return(Exponents, clearArray: false); ulongPool.Return(Exponents, clearArray: false);
ulongPool.Return(FilteredDivisors, clearArray: false);
partialPool.Return(DivisorData, clearArray: false); partialPool.Return(DivisorData, clearArray: false);
intPool.Return(Offsets, clearArray: false); intPool.Return(Offsets, clearArray: false);
intPool.Return(Counts, clearArray: false); intPool.Return(Counts, clearArray: false);
ulongPool.Return(Cycles, clearArray: false); ulongPool.Return(Cycles, clearArray: false);
Divisors = Array.Empty<ulong>(); Divisors = Array.Empty<ulong>();
Exponents = Array.Empty<ulong>(); Exponents = Array.Empty<ulong>();
FilteredDivisors = Array.Empty<ulong>();
DivisorData = Array.Empty<GpuDivisorPartialData>(); DivisorData = Array.Empty<GpuDivisorPartialData>();
Offsets = Array.Empty<int>(); Offsets = Array.Empty<int>();
Counts = Array.Empty<int>(); Counts = Array.Empty<int>();
@@ -936,3 +887,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession(); IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession();
} }
@@ -1,3 +1,4 @@
using System.Collections.Concurrent;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
@@ -8,29 +9,11 @@ namespace PerfectNumbers.Core.Gpu;
/// </summary> /// </summary>
public sealed class MersenneNumberDivisorGpuTester public sealed class MersenneNumberDivisorGpuTester
{ {
[ThreadStatic] private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>> _kernelCache = new();
private static Dictionary<Accelerator, Queue<MemoryBuffer1D<byte, Stride1D.Dense>>>? _resultBuffers; private readonly ConcurrentDictionary<Accelerator, MemoryBuffer1D<byte, Stride1D.Dense>> _resultBuffers = new();
private static Queue<MemoryBuffer1D<byte, Stride1D.Dense>> GetQueue(Accelerator accelerator) private Action<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>> GetKernel(Accelerator accelerator) =>
{ _kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>(DivisorKernels.Kernel));
var pool = _resultBuffers ??= [];
if (!pool.TryGetValue(accelerator, out var bufferQueue))
{
bufferQueue = [];
pool[accelerator] = bufferQueue;
}
return bufferQueue;
}
private static Action<AcceleratorStream, Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>> GetDivisorKernel(Accelerator accelerator)
{
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>(DivisorKernels.Kernel);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>>();
}
public static void BuildDivisorCandidates() public static void BuildDivisorCandidates()
{ {
@@ -52,39 +35,27 @@ public sealed class MersenneNumberDivisorGpuTester
_divisorCandidates = count == 0 ? [] : list[..count]; _divisorCandidates = count == 0 ? [] : list[..count];
} }
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
public bool IsDivisible(ulong exponent, in ReadOnlyGpuUInt128 divisor) public bool IsDivisible(ulong exponent, in ReadOnlyGpuUInt128 divisor)
{ {
// var accelerator = SharedGpuContext.CreateAccelerator(); var gpu = GpuContextPool.RentPreferred(preferCpu: false);
var acceleratorIndex = AcceleratorPool.Shared.Rent(); var accelerator = gpu.Accelerator;
var accelerator = _accelerators[acceleratorIndex]; var kernel = GetKernel(accelerator);
var stream = accelerator.CreateStream(); var resultBuffer = _resultBuffers.GetOrAdd(accelerator, acc => acc.Allocate1D<byte>(1));
Queue<MemoryBuffer1D<byte, Stride1D.Dense>> resultBuffers = GetQueue(accelerator);
if (!resultBuffers.TryDequeue(out var resultBuffer))
{
// lock(accelerator)
{
resultBuffer = accelerator.Allocate1D<byte>(1);
}
}
// There is no point in clearing this buffer. We always override item [0] and never use it beyond item [0] // There is no point in clearing this buffer. We always override item [0] and never use it beyond item [0]
// resultBuffer.MemSetToZero(stream); // resultBuffer.MemSetToZero();
var divisorKernel = GetDivisorKernel(accelerator); kernel(1, exponent, divisor, resultBuffer.View);
divisorKernel(stream, 1, exponent, divisor, resultBuffer.View); accelerator.Synchronize();
byte result = 0; Span<byte> result = stackalloc byte[1];
resultBuffer.View.CopyToCPU(stream, ref result, 1); resultBuffer.View.CopyToCPU(ref result[0], 1);
stream.Synchronize(); bool divisible = result[0] != 0;
bool divisible = result != 0; result[0] = 0;
resultBuffers.Enqueue(resultBuffer); resultBuffer.View.CopyFromCPU(ref result[0], 1);
stream.Dispose(); accelerator.Synchronize();
// AcceleratorPool.Shared.Return(accelerator); gpu.Dispose();
// accelerator.Dispose();
return divisible; 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) public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted)
{ {
@@ -12,45 +12,43 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
private readonly GpuKernelType _kernelType = kernelType; private readonly GpuKernelType _kernelType = kernelType;
private readonly bool _useGpuOrder = useGpuOrder; 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) 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})."); throw new NotImplementedException($"GPU incremental scanning requires the device cycle heuristics implementation (kernel: {_kernelType}, GPU order: {_useGpuOrder}).");
// GpuPrimeWorkLimiter.Acquire(); var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
var acceleratorIndex = AcceleratorPool.Shared.Rent(); var execution = gpuLease.EnterExecutionScope();
var accelerator = _accelerators[acceleratorIndex]; var accelerator = gpuLease.Accelerator;
var stream = accelerator.CreateStream(); var stream = gpuLease.Stream;
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy
UInt128 kStart = 1UL; UInt128 kStart = 1UL;
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 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 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 = gpuLease.Pow2ModKernel;
var pow2Kernel = kernels.Pow2Mod!; var incKernel = gpuLease.IncrementalKernel;
var incKernel = kernels.Incremental!;
ulong step10 = (exponent.Mod10() << 1).Mod10(); ulong step10 = (exponent.Mod10() << 1).Mod10();
ulong step8 = ((exponent & 7UL) << 1) & 7UL; ulong step8 = ((exponent & 7UL) << 1) & 7UL;
ulong step3 = ((exponent % 3UL) << 1) % 3UL; ulong step3 = ((exponent % 3UL) << 1) % 3UL;
ulong step5 = ((exponent % 5UL) << 1) % 5UL; ulong step5 = ((exponent % 5UL) << 1) % 5UL;
GpuUInt128 twoPGpu = (GpuUInt128)twoP; GpuUInt128 twoPGpu = (GpuUInt128)twoP;
var smallCyclesView = GpuKernelPool.GetSmallCyclesOnDevice(kernels); var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
ResiduePrimeViews primeViews = default; ResiduePrimeViews primeViews = default;
if (_kernelType == GpuKernelType.Pow2Mod) if (_kernelType == GpuKernelType.Pow2Mod)
{ {
primeViews = GpuKernelPool.GetSmallPrimesOnDevice(kernels); primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(accelerator);
} }
var orderBuffer = accelerator.Allocate1D<ulong>(batchSize); var orderBuffer = accelerator.Allocate1D<ulong>(batchSize);
// Avoid giant stack allocations that can trigger StackOverflow when batchSize is large. // Avoid giant stack allocations that can trigger StackOverflow when batchSize is large.
// Rent a reusable array from the shared pool instead. // Rent a reusable array from the shared pool instead.
ArrayPool<ulong> ulongPool = ThreadStaticPools.UlongPool; ulong[] orderArray = ArrayPool<ulong>.Shared.Rent(batchSize);
ulong[] orderArray = ulongPool.Rent(batchSize);
UInt128 remaining; UInt128 remaining;
int currentSize; int currentSize;
int i; int i;
UInt128 q = UInt128.Zero; UInt128 q = UInt128.Zero;
try
{
while (kStart <= maxK && Volatile.Read(ref isPrime)) while (kStart <= maxK && Volatile.Read(ref isPrime))
{ {
remaining = maxK - kStart + UInt128.One; remaining = maxK - kStart + UInt128.One;
@@ -97,8 +95,8 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
smallCyclesView); smallCyclesView);
} }
orderBuffer.View.CopyToCPU(stream, ref MemoryMarshal.GetReference(orders), currentSize);
stream.Synchronize(); stream.Synchronize();
orderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(orders), currentSize);
if (_kernelType == GpuKernelType.Pow2Mod) if (_kernelType == GpuKernelType.Pow2Mod)
{ {
for (i = 0; i < currentSize; i++) for (i = 0; i < currentSize; i++)
@@ -124,10 +122,13 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
kStart += (UInt128)currentSize; kStart += (UInt128)currentSize;
} }
}
stream.Dispose(); finally
ulongPool.Return(orderArray); {
ArrayPool<ulong>.Shared.Return(orderArray);
orderBuffer.Dispose(); orderBuffer.Dispose();
// GpuPrimeWorkLimiter.Release(); execution.Dispose();
gpuLease.Dispose();
}
} }
} }
@@ -8,129 +8,46 @@ namespace PerfectNumbers.Core.Gpu;
public class MersenneNumberLucasLehmerGpuTester public class MersenneNumberLucasLehmerGpuTester
{ {
[ThreadStatic] private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>> KernelCache = new();
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>>? _lucasLehmerKernel; 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 Action<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>> GetKernel(Accelerator accelerator)
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>? _addSmallKernel;
[ThreadStatic]
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>? _subSmallKernel;
[ThreadStatic]
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>? _reduceKernel;
[ThreadStatic]
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>>? _isZeroKernel;
[ThreadStatic]
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>>>? _lucasLehmerBatchKernel;
private static Action<AcceleratorStream, Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>> GetLucasLehmerKernel(Accelerator accelerator)
{ {
var pool = _lucasLehmerKernel ??= []; return KernelCache.GetOrAdd(accelerator, accel => accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>(LucasLehmerKernels.Kernel));
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
} }
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>(LucasLehmerKernels.Kernel); private Action<Index1D, ulong, ArrayView<GpuUInt128>> GetAddSmallKernel(Accelerator accelerator) =>
AddSmallKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.AddSmallKernel));
var kernel = KernelUtil.GetKernel(loaded); private Action<Index1D, ulong, ArrayView<GpuUInt128>> GetSubSmallKernel(Accelerator accelerator) =>
SubSmallKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.SubtractSmallKernel));
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>>(); private Action<Index1D, ulong, ArrayView<GpuUInt128>> GetReduceKernel(Accelerator accelerator) =>
pool[accelerator] = cached; ReduceKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.ReduceModMersenneKernel));
return cached;
}
private static Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>> GetAddSmallKernel(Accelerator accelerator) private Action<Index1D, ArrayView<GpuUInt128>, ArrayView<byte>> GetIsZeroKernel(Accelerator accelerator) =>
{ IsZeroKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>(LucasLehmerKernels.IsZeroKernel));
var pool = _addSmallKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.AddSmallKernel); 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));
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;
}
// Configurable LL slice size to keep kernels short. Default 32. // Configurable LL slice size to keep kernels short. Default 32.
public int SliceSize = 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) public bool IsPrime(ulong exponent, bool runOnGpu)
{ {
// Early rejections aligned with incremental/order sieves, but safe for small p: // Early rejections aligned with incremental/order sieves, but safe for small p:
@@ -147,11 +64,13 @@ public class MersenneNumberLucasLehmerGpuTester
return false; return false;
} }
var limiter = GpuPrimeWorkLimiter.Acquire();
bool result; bool result;
if (exponent >= 128UL) if (exponent >= 128UL)
{ {
if (!TryGetNttParameters(exponent, out var nttMod, out var primitiveRoot)) if (!TryGetNttParameters(exponent, out var nttMod, out var primitiveRoot))
{ {
limiter.Dispose();
throw new NotSupportedException("NTT parameters for the given exponent are not available."); throw new NotSupportedException("NTT parameters for the given exponent are not available.");
} }
@@ -159,22 +78,19 @@ public class MersenneNumberLucasLehmerGpuTester
} }
else else
{ {
// GpuPrimeWorkLimiter.Acquire(); var gpu = GpuContextPool.RentPreferred(preferCpu: !runOnGpu);
var acceleratorIndex = AcceleratorPool.Shared.Rent(); var accelerator = gpu.Accelerator;
var accelerator = _accelerators[acceleratorIndex]; var kernel = GetKernel(accelerator);
var stream = accelerator.CreateStream();
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 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 buffer = accelerator.Allocate1D<GpuUInt128>(1);
var lucasLehmerKernel = GetLucasLehmerKernel(accelerator); kernel(1, exponent, modulus, buffer.View);
lucasLehmerKernel(stream, 1, exponent, modulus, buffer.View); accelerator.Synchronize();
stream.Synchronize();
// TODO: Replace this with .CopyToCPU with pre-allocated buffer using stream. Synchronize on the stream after copy.
result = buffer.GetAsArray1D()[0].IsZero; result = buffer.GetAsArray1D()[0].IsZero;
buffer.Dispose(); buffer.Dispose();
stream.Dispose(); gpu.Dispose();
} }
// GpuPrimeWorkLimiter.Release(); limiter.Dispose();
return result; return result;
} }
@@ -194,18 +110,16 @@ public class MersenneNumberLucasLehmerGpuTester
} }
} }
var acceleratorIndex = AcceleratorPool.Shared.Rent(); var gpu = GpuContextPool.RentPreferred(preferCpu: false);
var accelerator = _accelerators[acceleratorIndex]; var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream(); var kernel = GetBatchKernel(accelerator);
var expBuffer = accelerator.Allocate1D<ulong>(count); var expBuffer = accelerator.Allocate1D<ulong>(count);
ArrayPool<ulong> ulongPool = ThreadStaticPools.UlongPool; ulong[] expArray = ArrayPool<ulong>.Shared.Rent(count);
ulong[] expArray = ulongPool.Rent(count);
exponents.CopyTo(expArray); 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 = ArrayPool<GpuUInt128>.Shared.Rent(count);
GpuUInt128[] modulusArray = gpuUInt128Pool.Rent(count);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
// TODO: Replace this per-exponent shift with a small shared table (one entry per supported exponent < 128) // 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); 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 stateBuffer = accelerator.Allocate1D<GpuUInt128>(count);
var lucasLehmerBatchKernel = GetLucasLehmerBatchKernel(accelerator); kernel(count, expBuffer.View, modBuffer.View, stateBuffer.View);
lucasLehmerBatchKernel(stream, count, expBuffer.View, modBuffer.View, stateBuffer.View); accelerator.Synchronize();
stateBuffer.View.CopyToCPU(stream, ref residues[0], count); stateBuffer.View.CopyToCPU(ref residues[0], count);
stream.Synchronize();
stateBuffer.Dispose(); stateBuffer.Dispose();
modBuffer.Dispose(); modBuffer.Dispose();
expBuffer.Dispose(); expBuffer.Dispose();
gpuUInt128Pool.Return(modulusArray); ArrayPool<GpuUInt128>.Shared.Return(modulusArray);
ulongPool.Return(expArray); ArrayPool<ulong>.Shared.Return(expArray);
stream.Dispose(); gpu.Dispose();
} }
public void IsMersennePrimeBatch(ReadOnlySpan<ulong> exponents, Span<bool> results) 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)); throw new ArgumentException("Result span too small", nameof(results));
} }
ArrayPool<GpuUInt128> gpuUInt128Pool = ThreadStaticPools.GpuUInt128Pool; GpuUInt128[] buffer = ArrayPool<GpuUInt128>.Shared.Rent(count);
GpuUInt128[] buffer = gpuUInt128Pool.Rent(count); try
{
ComputeResidues(exponents, buffer.AsSpan(0, count)); ComputeResidues(exponents, buffer.AsSpan(0, count));
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
results[i] = buffer[i].IsZero; results[i] = buffer[i].IsZero;
} }
gpuUInt128Pool.Return(buffer);
} }
finally
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; {
ArrayPool<GpuUInt128>.Shared.Return(buffer);
}
}
private bool IsMersennePrimeNtt(ulong exponent, GpuUInt128 nttMod, GpuUInt128 primitiveRoot, bool runOnGpu) private bool IsMersennePrimeNtt(ulong exponent, GpuUInt128 nttMod, GpuUInt128 primitiveRoot, bool runOnGpu)
{ {
var acceleratorIndex = AcceleratorPool.Shared.Rent(); var gpu = GpuContextPool.RentPreferred(preferCpu: !runOnGpu);
var accelerator = _accelerators[acceleratorIndex]; var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream(); var addKernel = GetAddSmallKernel(accelerator);
var subKernel = GetSubSmallKernel(accelerator); var subKernel = GetSubSmallKernel(accelerator);
var reduceKernel = GetReduceKernel(accelerator); var reduceKernel = GetReduceKernel(accelerator);
var isZeroKernel = GetIsZeroKernel(accelerator); var zeroKernel = GetIsZeroKernel(accelerator);
int limbCount = (int)((exponent + 127UL) / 128UL); int limbCount = (int)((exponent + 127UL) / 128UL);
int target = limbCount * 2; int target = limbCount * 2;
@@ -268,9 +183,8 @@ public class MersenneNumberLucasLehmerGpuTester
transformLength <<= 1; transformLength <<= 1;
} }
var stateBuffer = accelerator.Allocate1D<GpuUInt128>(transformLength); var stateBuffer = accelerator.Allocate1D<GpuUInt128>(transformLength);
stateBuffer.MemSetToZero(stream); stateBuffer.MemSetToZero();
var addSmallKernel = GetAddSmallKernel(accelerator); addKernel(1, 4UL, stateBuffer.View);
addSmallKernel(stream, 1, 4UL, stateBuffer.View);
// TODO(LL-SLICE): Slice Lucas–Lehmer iterations into short batches to // TODO(LL-SLICE): Slice Lucas–Lehmer iterations into short batches to
// avoid long-running kernels and TDR. Example: process 8–64 iterations // 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++) for (int s = 0; s < iter; s++)
{ {
// Square in NTT domain with staged kernels (short runtime per kernel). // Square in NTT domain with staged kernels (short runtime per kernel).
NttGpuMath.SquareDevice(accelerator, stream, stateBuffer.View, nttMod, primitiveRoot); NttGpuMath.SquareDevice(accelerator, stateBuffer.View, nttMod, primitiveRoot);
subKernel(stream, 1, 2UL, stateBuffer.View); subKernel(1, 2UL, stateBuffer.View);
reduceKernel(stream, 1, exponent, stateBuffer.View); reduceKernel(1, exponent, stateBuffer.View);
} }
// Synchronize between slices to yield to scheduler and avoid TDR. // Synchronize between slices to yield to scheduler and avoid TDR.
accelerator.Synchronize();
i += (ulong)iter; i += (ulong)iter;
} }
reduceKernel(stream, 1, exponent, stateBuffer.View); reduceKernel(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();
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; bool isPrime = result[0] != 0;
resultBuffer.Dispose(); resultBuffer.Dispose();
stateBuffer.Dispose(); stateBuffer.Dispose();
stream.Dispose(); gpu.Dispose();
return isPrime; return isPrime;
} }
@@ -573,4 +488,6 @@ public class MersenneNumberLucasLehmerGpuTester
return result; return result;
} }
} }
@@ -7,46 +7,36 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO
private readonly GpuKernelType _kernelType = kernelType; private readonly GpuKernelType _kernelType = kernelType;
private readonly bool _useGpuOrder = useGpuOrder; 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) public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
{ {
// GpuPrimeWorkLimiter.Acquire(); var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
// var accelerator = SharedGpuContext.Accelerator; var execution = gpuLease.EnterExecutionScope();
var acceleratorIndex = AcceleratorPool.Shared.Rent(); var accelerator = gpuLease.Accelerator;
var accelerator = _accelerators[acceleratorIndex]; var stream = gpuLease.Stream;
var stream = accelerator.CreateStream();
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR
UInt128 kStart = UInt128.One; UInt128 kStart = UInt128.One;
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 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 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 var kernel = _kernelType switch
{ {
GpuKernelType.Pow2Mod => kernels.Pow2ModOrder!, GpuKernelType.Pow2Mod => gpuLease.Pow2ModOrderKernel,
_ => kernels.IncrementalOrder!, _ => gpuLease.IncrementalOrderKernel,
}; };
MemoryBuffer1D<int, ILGPU.Stride1D.Dense>? foundBuffer; 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.
// 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); exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
try
{
UInt128 remaining; UInt128 remaining;
int currentSize; int currentSize;
int found = 0; int found = 0;
var smallCyclesView = GpuKernelPool.GetSmallCyclesOnDevice(kernels);
while (kStart <= maxK && Volatile.Read(ref isPrime)) while (kStart <= maxK && Volatile.Read(ref isPrime))
{ {
remaining = maxK - kStart + 1UL; remaining = maxK - kStart + 1UL;
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining; 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. // 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); foundBuffer.MemSetToZero();
// Precompute residue automaton bases for this batch // Precompute residue automaton bases for this batch
UInt128 q0 = twoP * kStart + 1UL; UInt128 q0 = twoP * kStart + 1UL;
// TODO: Replace these modulo recomputations with the divisor-cycle stepping deltas captured in // TODO: Replace these modulo recomputations with the divisor-cycle stepping deltas captured in
@@ -55,14 +45,14 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3); 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); 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); // 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(); stream.Synchronize();
foundBuffer.View.CopyToCPU(ref found, 1);
if (found != 0) if (found != 0)
{ {
Volatile.Write(ref isPrime, false); Volatile.Write(ref isPrime, false);
@@ -71,9 +61,13 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO
kStart += (UInt128)currentSize; kStart += (UInt128)currentSize;
} }
}
stream.Dispose(); finally
{
foundBuffer.Dispose(); foundBuffer.Dispose();
// GpuPrimeWorkLimiter.Release(); execution.Dispose();
gpuLease.Dispose();
}
} }
} }
Loaded 100 of 135 files, more files were not shown because too many files have changed in this diff. Show more