Compare commits

...
Author SHA1 Message Date
mlemanczyk fc22c01ec8 Various improvements to make sure scratch buffers don't override each others memory 2025-11-03 23:49:55 +01:00
Marek Lemańczyk dcda4b5cba Merge branch 'codex/optimize-evenperfectbitscanner-for-static-arrays' from github 2025-11-03 18:39:00 +01:00
Marek Lemańczyk 507daaddb9 Merge pull request #253 from mlemanczyk/codex/optimize-evenperfectbitscanner-for-primetestergpucontextleas
Add GPU lease warm-up and shared limiter
2025-11-02 14:02:42 +01:00
Marek Lemańczyk 1cbe7bf869 Use GpuPrimeWorkLimiter for GPU lease limiting 2025-11-02 14:02:15 +01:00
Marek Lemańczyk 00d4e9bde2 Add GPU lease warm-up and shared limiter 2025-11-02 13:47:57 +01:00
mlemanczyk 0e7bbae132 GPU contexts and accelerators made shared to prevent application crashes with high no. of threads. 2025-11-02 12:51:31 +01:00
mlemanczyk b33a3c8e75 Compilation error fixed 2025-11-02 11:25:41 +01:00
mlemanczyk 07be671726 Various small optimizations 2025-11-02 11:24:18 +01:00
Marek Lemańczyk 76dfe108a9 Address feedback on GPU pooling and heuristic factoring 2025-11-02 11:22:49 +01:00
Marek Lemańczyk 5339c3c304 Use span growth instead of dictionaries in partial factor 2025-11-02 11:22:38 +01:00
Marek Lemańczyk ab6a416de2 Warm GPU resources before thread startup 2025-11-02 11:22:28 +01:00
mlemanczyk 2281d9535e Revert "Merge pull request #251 from mlemanczyk/codex/refactor-prime-checking-on-gpu-n5t2pm"
This reverts commit 936ce239db, reversing
changes made to e14b77e504.
2025-11-02 11:22:14 +01:00
Marek Lemańczyk 936ce239db Merge pull request #251 from mlemanczyk/codex/refactor-prime-checking-on-gpu-n5t2pm
Warm GPU resources before thread startup
2025-11-01 04:09:00 +01:00
mlemanczyk 22f20bfe98 Various small optimizations 2025-11-01 03:42:36 +01:00
Marek Lemańczyk b7c34f4960 Preload divisor generator tables for GPU pool contexts 2025-10-31 23:01:28 +01:00
Marek Lemańczyk 469c8057d3 Flatten heuristic trial division bounds checks 2025-10-31 22:45:59 +01:00
Marek Lemańczyk 8c662c5c85 Address feedback on GPU pooling and heuristic factoring 2025-10-31 21:47:42 +01:00
Marek Lemańczyk 567b92792c Use span growth instead of dictionaries in partial factor 2025-10-31 18:52:00 +01:00
Marek Lemańczyk 6864b316d5 Warm GPU resources before thread startup 2025-10-31 16:56:25 +01:00
mlemanczyk e14b77e504 Batch size made dynamic - dependent on the value 2025-10-30 02:43:15 +01:00
Marek Lemańczyk 30af16a4f6 Merge pull request #249 from mlemanczyk/codex/optimize-heuristiccombinedprimetester-calculations
Use divisor square tables in heuristic combined prime tester
2025-10-29 22:31:52 +01:00
mlemanczyk fc5700f82c IsPrime with cancellation token removed. 2025-10-29 22:31:28 +01:00
Marek Lemańczyk f9504a6a19 Use atomic flag writes in heuristic trial division 2025-10-29 20:40:40 +01:00
Marek Lemańczyk 5ac05489c1 Use precomputed divisor squares in heuristic prime tester 2025-10-29 20:22:41 +01:00
Marek Lemańczyk f7a0627ce5 Align heuristic prime testers with squared limits 2025-10-29 19:35:59 +01:00
Marek Lemańczyk 5536dc4865 Fix GPU by-divisor hang for small primes 2025-10-29 19:21:41 +01:00
Marek Lemańczyk c61df1c33b Inline batch copies for GPU prime tester 2025-10-29 19:21:32 +01:00
Marek Lemańczyk 2b0fc5be13 Pool reusable GPU leases with embedded kernel state 2025-10-29 18:22:40 +01:00
Marek Lemańczyk a4ac82e74d Inline GPU kernel state into prime tester leases 2025-10-29 18:13:51 +01:00
Marek Lemańczyk 23fa56b356 Embed kernel state lookup in GPU leases 2025-10-29 18:13:46 +01:00
Marek Lemańczyk 08499fdda1 Refine GPU prime tester leasing 2025-10-29 17:39:38 +01:00
Marek Lemańczyk 6395b06b19 Optimize single GPU prime path and reuse lease buffers 2025-10-29 17:20:21 +01:00
Marek Lemańczyk 83a6c6c558 Scope GPU prime tester buffers to leases 2025-10-29 17:05:17 +01:00
Marek Lemańczyk ca53ddc798 Inline GPU lease buffers into the context lease 2025-10-29 16:53:29 +01:00
Marek Lemańczyk aab785650d Manage GPU lease buffers without kernel scratch pool 2025-10-29 16:53:22 +01:00
Marek Lemańczyk 13b59abb1c Embed GPU scratch buffers in context leases 2025-10-29 16:25:38 +01:00
Marek Lemańczyk b753654ebb Enforce valid divisor selectors in heuristic combined tester 2025-10-29 15:32:40 +01:00
Marek Lemańczyk b0158a844a Use divisor squares to bound heuristic combined trial division 2025-10-29 15:13:54 +01:00
mlemanczyk 4ab77ba170 Merge branch 'codex/analyze-gc-instances-in-checkdivisors-method' of https://github.com/mlemanczyk/even-perfect-numbers-scanner into codex/analyze-gc-instances-in-checkdivisors-method 2025-10-29 14:06:08 +01:00
Marek Lemańczyk 47eee81d3c Merge pull request #248 from mlemanczyk/codex/optimize-heuristicprimetester-for-divisor-selection
Add filtered group B divisor tables for heuristic GPU checks
2025-10-29 14:05:02 +01:00
mlemanczyk b2865b54ce Tiny optimization 2025-10-29 14:04:25 +01:00
mlemanczyk 4c79c4087e Various optimizations 2025-10-29 14:00:38 +01:00
mlemanczyk 3147728d5b Benchmarks for heuristic combined methods added 2025-10-29 04:53:13 +01:00
mlemanczyk 880b1cd962 Revert "Unroll divisor-by-divisor GPU kernel loops"
This reverts commit 5d14efb739.
2025-10-29 04:51:28 +01:00
Marek Lemańczyk 5d14efb739 Unroll divisor-by-divisor GPU kernel loops 2025-10-29 04:31:45 +01:00
Marek Lemańczyk f49963c4f6 Restore Math.Sqrt heuristic bound 2025-10-29 01:43:12 +01:00
Marek Lemańczyk 5a9e103bd5 Replace heuristic sqrt with log2-based estimate 2025-10-29 01:29:12 +01:00
Marek Lemańczyk eeeff42be6 Replace heuristic sqrt with integer algorithm 2025-10-29 01:02:31 +01:00
Marek Lemańczyk b54f4b6bd6 Simplify combined heuristic divisor selection 2025-10-29 00:44:46 +01:00
Marek Lemańczyk b2cb17a96f Return to one-A-one-B combined schedule 2025-10-29 00:44:39 +01:00
Marek Lemańczyk f993561cd5 Eagerly build combined divisors and restore two-A-one-B default 2025-10-28 23:43:15 +01:00
Marek Lemańczyk e8d163f595 Add three-A-two-B combined divisor schedule 2025-10-28 23:22:16 +01:00
Marek Lemańczyk 465b40bf35 Add three-A-one-B schedule to combined tester 2025-10-28 23:09:41 +01:00
Marek Lemańczyk 4c445f2a42 Switch combined heuristic divisors to 1A-1B schedule 2025-10-28 22:55:17 +01:00
Marek Lemańczyk 41bff698e8 Add combined heuristic prime tester with shared divisor arrays 2025-10-28 22:40:51 +01:00
mlemanczyk 0850227881 Switched to heuristic GPU for a better performance than the standard one. 2025-10-28 22:15:33 +01:00
Marek Lemańczyk c693a05708 Add reusable group B divisor tables without trailing-three primes 2025-10-28 21:56:18 +01:00
Marek Lemańczyk b83bddf49a Merge pull request #247 from mlemanczyk/codex/fix-unhandled-exception-in-evenperfectbitscanner
Fix GPU Montgomery multiply to avoid unsupported intrinsics
2025-10-28 21:10:09 +01:00
mlemanczyk e5f5cd563e Merge branch 'codex/fix-unhandled-exception-in-evenperfectbitscanner' of https://github.com/mlemanczyk/even-perfect-numbers-scanner into codex/fix-unhandled-exception-in-evenperfectbitscanner 2025-10-28 21:08:14 +01:00
Marek Lemańczyk 3304335072 Rename CPU Montgomery multiply and align GPU helper 2025-10-28 21:06:40 +01:00
mlemanczyk ab045a62c4 Revert "Temporarily enabling heuristic GPU order checks."
This reverts commit 4db06cfe80.
2025-10-28 21:04:09 +01:00
Marek Lemańczyk eaee599f25 Use GPU-safe Montgomery multiply in kernels 2025-10-28 20:58:02 +01:00
mlemanczyk 4db06cfe80 Temporarily enabling heuristic GPU order checks. 2025-10-28 20:49:41 +01:00
mlemanczyk bead40713c Switching .IsPrime check to CPU for much higher performance. 2025-10-28 20:49:11 +01:00
mlemanczyk ca8ab2bcf8 Tester made static to better mimic production code. 2025-10-28 20:48:35 +01:00
Marek Lemańczyk d149700311 Merge pull request #246 from mlemanczyk/codex/compare-cpu-and-gpu-execution-paths
Make GPU sieve results authoritative
2025-10-28 20:14:33 +01:00
Marek Lemańczyk 1792349bad Make GPU sieve results authoritative 2025-10-28 20:14:22 +01:00
mlemanczyk 2f461ee001 Merge branch 'codex/analyze-gc-instances-in-checkdivisors-method' of https://github.com/mlemanczyk/even-perfect-numbers-scanner into codex/analyze-gc-instances-in-checkdivisors-method 2025-10-28 20:12:45 +01:00
mlemanczyk 93aed56f23 Non-heuristic GPU benchmark uncommented, since we improved it. 2025-10-28 20:12:41 +01:00
Marek Lemańczyk ff9774925e Merge pull request #245 from mlemanczyk/codex/move-buildsmallprimes-to-divisorgenerator-g256a9
Enable residue-aware GPU sieve in PrimeTester
2025-10-28 20:00:22 +01:00
Marek Lemańczyk 1f9421430b Reuse GPU divisor tables across PrimeTester batches 2025-10-28 19:53:11 +01:00
Marek Lemańczyk c53966e481 Enable residue-aware GPU sieve in PrimeTester 2025-10-28 18:39:18 +01:00
mlemanczyk 3d43d52f2e Console logging updated 2025-10-28 16:04:42 +01:00
Marek Lemańczyk fa4bb0bcc1 Merge pull request #243 from mlemanczyk/codex/modify-evenperfectbitscanner-to-use-taskthreadpool
Refactor by-divisor scheduling to use task thread pool
2025-10-28 15:47:47 +01:00
Marek Lemańczyk c4c783610b Add start gate to by-divisor task scheduling 2025-10-28 15:32:15 +01:00
Marek Lemańczyk 29f58e3059 Refactor by-divisor scheduling to use task thread pool 2025-10-28 15:11:21 +01:00
Marek Lemańczyk 29af0cb0ce Merge pull request #242 from mlemanczyk/codex/modify-taskthreadpool-to-terminate-threads-after-tasks
Stop tracking double disposal in TaskThreadPool
2025-10-28 14:29:48 +01:00
Marek Lemańczyk 3edf2cd575 Stop tracking double disposal in TaskThreadPool 2025-10-28 14:29:34 +01:00
Marek Lemańczyk a89ed7576e Merge pull request #241 from mlemanczyk/codex/remove-executionscope-structure
Remove GPU context creation lock
2025-10-28 13:39:43 +01:00
Marek Lemańczyk 86990ae0f7 Remove kernel initialization lock from GPU leases 2025-10-28 13:30:47 +01:00
Marek Lemańczyk de7ddd8db3 Remove GPU context creation lock 2025-10-28 13:26:20 +01:00
mlemanczyk 139899fa5e More console logs added. 2025-10-28 02:11:24 +01:00
Marek Lemańczyk c39a99a4fd Merge pull request #240 from mlemanczyk/codex/analyze-mersenne=bydivisor-loops-for-early-breaks-kynlfe
Remove temporary by-divisor review documents
2025-10-28 02:01:52 +01:00
Marek Lemańczyk 6dd5cea79f Remove temporary by-divisor review documents 2025-10-28 02:01:28 +01:00
Marek Lemańczyk bd0727131f Merge pull request #238 from mlemanczyk/codex/optimize-evenperfectbitscanner-loops-kit1so
Hoist prime order thresholds in CPU heuristics
2025-10-27 21:31:23 +01:00
Marek Lemańczyk 968dd89d0d Remove by-divisor path map document 2025-10-27 21:28:41 +01:00
Marek Lemańczyk 94a4ed8328 Hoist prime order thresholds in CPU heuristics 2025-10-27 21:24:43 +01:00
mlemanczyk d381abe0cc We'll now clear the dictionary after renting, to avoid unnecessary clearing if it's not going to be used anymore. 2025-10-27 16:54:49 +01:00
mlemanczyk 50d66a5186 Switched to GPU version in some places. 2025-10-27 15:30:10 +01:00
66 changed files with 3998 additions and 2016 deletions

No files matched your search

@@ -32,7 +32,7 @@ public class GpuUInt128Montgomery64Benchmarks
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public ulong ExtensionMontgomeryMultiply() public ulong ExtensionMontgomeryMultiply()
{ {
return Input.Left.MontgomeryMultiply(Input.Right, Input.Modulus, Input.NPrime); return Input.Left.MontgomeryMultiplyCpu(Input.Right, Input.Modulus, Input.NPrime);
} }
/// <summary> /// <summary>
@@ -640,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.MontgomeryMultiply(montgomeryTwo, montgomeryTwo, modulus, nPrime); ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiplyCpu(montgomeryTwo, montgomeryTwo, modulus, nPrime);
return new MontgomeryDivisorData( return new MontgomeryDivisorData(
modulus, modulus,
@@ -736,22 +736,22 @@ public class Pow2ModBenchmarks
if (exponent == 0UL) if (exponent == 0UL)
{ {
return result.MontgomeryMultiply(1UL, modulus, nPrime); return result.MontgomeryMultiplyCpu(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.MontgomeryMultiply(result, modulus, nPrime); result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
if (((exponent >> bitIndex) & 1UL) != 0UL) if (((exponent >> bitIndex) & 1UL) != 0UL)
{ {
result = result.MontgomeryMultiply(baseVal, modulus, nPrime); result = result.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
} }
} }
return result.MontgomeryMultiply(1UL, modulus, nPrime); return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
} }
private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor) private static ulong Pow2MontgomeryModBatched4(ulong exponent, in MontgomeryDivisorData divisor)
@@ -772,35 +772,35 @@ public class Pow2ModBenchmarks
ulong nibble = remainingExponent & 0xFUL; ulong nibble = remainingExponent & 0xFUL;
ulong base0 = baseVal; ulong base0 = baseVal;
ulong base1 = base0.MontgomeryMultiply(base0, modulus, nPrime); ulong base1 = base0.MontgomeryMultiplyCpu(base0, modulus, nPrime);
ulong base2 = base1.MontgomeryMultiply(base1, modulus, nPrime); ulong base2 = base1.MontgomeryMultiplyCpu(base1, modulus, nPrime);
ulong base3 = base2.MontgomeryMultiply(base2, modulus, nPrime); ulong base3 = base2.MontgomeryMultiplyCpu(base2, modulus, nPrime);
if ((nibble & 1UL) != 0UL) if ((nibble & 1UL) != 0UL)
{ {
result = result.MontgomeryMultiply(base0, modulus, nPrime); result = result.MontgomeryMultiplyCpu(base0, modulus, nPrime);
} }
if ((nibble & 2UL) != 0UL) if ((nibble & 2UL) != 0UL)
{ {
result = result.MontgomeryMultiply(base1, modulus, nPrime); result = result.MontgomeryMultiplyCpu(base1, modulus, nPrime);
} }
if ((nibble & 4UL) != 0UL) if ((nibble & 4UL) != 0UL)
{ {
result = result.MontgomeryMultiply(base2, modulus, nPrime); result = result.MontgomeryMultiplyCpu(base2, modulus, nPrime);
} }
if ((nibble & 8UL) != 0UL) if ((nibble & 8UL) != 0UL)
{ {
result = result.MontgomeryMultiply(base3, modulus, nPrime); result = result.MontgomeryMultiplyCpu(base3, modulus, nPrime);
} }
baseVal = base3.MontgomeryMultiply(base3, modulus, nPrime); baseVal = base3.MontgomeryMultiplyCpu(base3, modulus, nPrime);
remainingExponent >>= 4; remainingExponent >>= 4;
} }
return result.MontgomeryMultiply(1UL, modulus, nPrime); return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
} }
private static ulong Pow2MontgomeryModSlidingWindow(ulong exponent, in MontgomeryDivisorData divisor) private static ulong Pow2MontgomeryModSlidingWindow(ulong exponent, in MontgomeryDivisorData divisor)
@@ -817,16 +817,16 @@ public class Pow2ModBenchmarks
if (exponent == 0UL) if (exponent == 0UL)
{ {
return result.MontgomeryMultiply(1UL, modulus, nPrime); return result.MontgomeryMultiplyCpu(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.MontgomeryMultiply(baseVal, modulus, nPrime); ulong baseSquared = baseVal.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
for (int i = 1; i < oddPowers.Length; i++) for (int i = 1; i < oddPowers.Length; i++)
{ {
oddPowers[i] = oddPowers[i - 1].MontgomeryMultiply(baseSquared, modulus, nPrime); oddPowers[i] = oddPowers[i - 1].MontgomeryMultiplyCpu(baseSquared, modulus, nPrime);
} }
int bitIndex = 63 - BitOperations.LeadingZeroCount(exponent); int bitIndex = 63 - BitOperations.LeadingZeroCount(exponent);
@@ -835,7 +835,7 @@ public class Pow2ModBenchmarks
{ {
if (((exponent >> bitIndex) & 1UL) == 0UL) if (((exponent >> bitIndex) & 1UL) == 0UL)
{ {
result = result.MontgomeryMultiply(result, modulus, nPrime); result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
bitIndex--; bitIndex--;
continue; continue;
} }
@@ -852,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.MontgomeryMultiply(result, modulus, nPrime); result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
} }
result = result.MontgomeryMultiply(oddPowers[(int)(windowValue >> 1)], modulus, nPrime); result = result.MontgomeryMultiplyCpu(oddPowers[(int)(windowValue >> 1)], modulus, nPrime);
bitIndex = windowStart - 1; bitIndex = windowStart - 1;
} }
return result.MontgomeryMultiply(1UL, modulus, nPrime); return result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
} }
private static ulong Pow2ModBinary(ulong exponent, ulong modulus) private static ulong Pow2ModBinary(ulong exponent, ulong modulus)
@@ -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.MontgomeryMultiply(montgomeryTwo, montgomeryTwo, modulus, nPrime); ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiplyCpu(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.MontgomeryMultiply(1UL, modulus, nPrime); return result.MontgomeryMultiplyCpu(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.MontgomeryMultiply(result, modulus, nPrime); result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
if (((exponent >> bitIndex) & 1UL) != 0UL) if (((exponent >> bitIndex) & 1UL) != 0UL)
{ {
result = result.MontgomeryMultiply(baseVal, modulus, nPrime); result = result.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
} }
} }
return result.MontgomeryMultiply(1UL, modulus, nPrime); return result.MontgomeryMultiplyCpu(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.MontgomeryMultiply(base0, modulus, nPrime); ulong base1 = base0.MontgomeryMultiplyCpu(base0, modulus, nPrime);
ulong base2 = base1.MontgomeryMultiply(base1, modulus, nPrime); ulong base2 = base1.MontgomeryMultiplyCpu(base1, modulus, nPrime);
ulong base3 = base2.MontgomeryMultiply(base2, modulus, nPrime); ulong base3 = base2.MontgomeryMultiplyCpu(base2, modulus, nPrime);
if ((nibble & 1UL) != 0UL) if ((nibble & 1UL) != 0UL)
{ {
result = result.MontgomeryMultiply(base0, modulus, nPrime); result = result.MontgomeryMultiplyCpu(base0, modulus, nPrime);
} }
if ((nibble & 2UL) != 0UL) if ((nibble & 2UL) != 0UL)
{ {
result = result.MontgomeryMultiply(base1, modulus, nPrime); result = result.MontgomeryMultiplyCpu(base1, modulus, nPrime);
} }
if ((nibble & 4UL) != 0UL) if ((nibble & 4UL) != 0UL)
{ {
result = result.MontgomeryMultiply(base2, modulus, nPrime); result = result.MontgomeryMultiplyCpu(base2, modulus, nPrime);
} }
if ((nibble & 8UL) != 0UL) if ((nibble & 8UL) != 0UL)
{ {
result = result.MontgomeryMultiply(base3, modulus, nPrime); result = result.MontgomeryMultiplyCpu(base3, modulus, nPrime);
} }
baseVal = base3.MontgomeryMultiply(base3, modulus, nPrime); baseVal = base3.MontgomeryMultiplyCpu(base3, modulus, nPrime);
remainingExponent >>= 4; remainingExponent >>= 4;
} }
return result.MontgomeryMultiply(1UL, modulus, nPrime); return result.MontgomeryMultiplyCpu(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.MontgomeryMultiply(1UL, modulus, nPrime); return result.MontgomeryMultiplyCpu(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.MontgomeryMultiply(baseVal, modulus, nPrime); ulong baseSquared = baseVal.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
for (int i = 1; i < oddPowers.Length; i++) for (int i = 1; i < oddPowers.Length; i++)
{ {
oddPowers[i] = oddPowers[i - 1].MontgomeryMultiply(baseSquared, modulus, nPrime); oddPowers[i] = oddPowers[i - 1].MontgomeryMultiplyCpu(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.MontgomeryMultiply(result, modulus, nPrime); result = result.MontgomeryMultiplyCpu(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.MontgomeryMultiply(result, modulus, nPrime); result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
} }
result = result.MontgomeryMultiply(oddPowers[(int)(windowValue >> 1)], modulus, nPrime); result = result.MontgomeryMultiplyCpu(oddPowers[(int)(windowValue >> 1)], modulus, nPrime);
bitIndex = windowStart - 1; bitIndex = windowStart - 1;
} }
return result.MontgomeryMultiply(1UL, modulus, nPrime); return result.MontgomeryMultiplyCpu(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,6 +1,3 @@
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;
@@ -12,8 +9,7 @@ namespace EvenPerfectBitScanner.Benchmarks;
[MemoryDiagnoser] [MemoryDiagnoser]
public class PrimeTesterBenchmarks public class PrimeTesterBenchmarks
{ {
private HeuristicPrimeTester _cpuTester = null!; private static readonly HeuristicPrimeTester _heuristicTester = new();
private HeuristicPrimeTester _gpuTester = null!;
private ulong[] _candidates = []; private ulong[] _candidates = [];
[ParamsSource(nameof(GetBenchmarkCases))] [ParamsSource(nameof(GetBenchmarkCases))]
@@ -22,8 +18,6 @@ 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)
@@ -31,23 +25,21 @@ public class PrimeTesterBenchmarks
return; return;
} }
CancellationToken cancellationToken = CancellationToken.None; _ = _heuristicTester.IsPrimeCpu(_candidates[0]);
_cpuTester.IsPrimeCpu(_candidates[0], cancellationToken); _ = _heuristicTester.IsPrimeGpu(_candidates[0]);
_gpuTester.IsPrimeGpu(_candidates[0]);
_ = Prime.Numbers.IsPrime(_candidates[0]); _ = Prime.Numbers.IsPrime(_candidates[0]);
} }
[Benchmark(Baseline = true)] [Benchmark]
public int HeuristicCpu() public int HeuristicCpu()
{ {
HeuristicPrimeTester tester = _cpuTester; HeuristicPrimeTester tester = _heuristicTester;
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 (tester.IsPrimeCpu(values[i], cancellationToken)) if (tester.IsPrimeCpu(values[i]))
{ {
primeCount++; primeCount++;
} }
@@ -59,9 +51,8 @@ public class PrimeTesterBenchmarks
[Benchmark] [Benchmark]
public int HeuristicGpu() public int HeuristicGpu()
{ {
HeuristicPrimeTester tester = _gpuTester; HeuristicPrimeTester tester = _heuristicTester;
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++)
@@ -75,18 +66,15 @@ public class PrimeTesterBenchmarks
return primeCount; return primeCount;
} }
// This benchmark is very slow. No point in comparing it. [Benchmark]
// [Benchmark] public int HeuristicCombinedGpu()
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.Exclusive.IsPrimeGpu(values[i], cancellationToken)) if (HeuristicCombinedPrimeTester.IsPrimeGpu(values[i]))
{ {
primeCount++; primeCount++;
} }
@@ -96,16 +84,48 @@ public class PrimeTesterBenchmarks
} }
[Benchmark] [Benchmark]
public int NonHeuristicCpu() public int HeuristicCombinedCpu()
{ {
// 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.IsPrimeCpu(values[i], cancellationToken)) if (HeuristicCombinedPrimeTester.IsPrimeCpu(values[i]))
{
primeCount++;
}
}
return primeCount;
}
[Benchmark]
public int NonHeuristicGpu()
{
ulong[] values = _candidates;
int primeCount = 0;
for (int i = 0; i < values.Length; i++)
{
if (PrimeTester.IsPrimeGpu(values[i]))
{
primeCount++;
}
}
return primeCount;
}
[Benchmark(Baseline = true)]
public int NonHeuristicCpu()
{
ulong[] values = _candidates;
int primeCount = 0;
for (int i = 0; i < values.Length; i++)
{
if (PrimeTester.IsPrime(values[i]))
{ {
primeCount++; primeCount++;
} }
+1 -1
View File
@@ -55,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)
{ {
new PrimeTester().IsPrime(n, CancellationToken.None).Should().Be(expected); PrimeTester.IsPrime(n).Should().Be(expected);
} }
[Fact] [Fact]
+45 -34
View File
@@ -202,14 +202,15 @@ internal readonly struct CliArguments
if (argument.StartsWith("--prime=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--prime=", StringComparison.OrdinalIgnoreCase))
{ {
startPrime = Utf8CliParser.ParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1)); ReadOnlySpan<char> primeValue = argument.AsSpan("--prime=".Length);
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(argument.IndexOf('=') + 1); ReadOnlySpan<char> value = argument.AsSpan("--max-prime=".Length);
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;
@@ -238,13 +239,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(argument.IndexOf('=') + 1))); threadCount = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--threads=".Length)));
continue; continue;
} }
if (argument.StartsWith("--mersenne=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--mersenne=", StringComparison.OrdinalIgnoreCase))
{ {
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1); ReadOnlySpan<char> value = argument.AsSpan("--mersenne=".Length);
if (value.Equals("pow2mod", StringComparison.OrdinalIgnoreCase)) if (value.Equals("pow2mod", StringComparison.OrdinalIgnoreCase))
{ {
kernelType = GpuKernelType.Pow2Mod; kernelType = GpuKernelType.Pow2Mod;
@@ -281,7 +282,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(argument.IndexOf('=') + 1), out ulong parsedLimit)) if (Utf8CliParser.TryParseUInt64(argument.AsSpan("--divisor-cycles-limit=".Length), out ulong parsedLimit))
{ {
divisorCyclesSearchLimit = parsedLimit; divisorCyclesSearchLimit = parsedLimit;
} }
@@ -291,7 +292,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(argument.IndexOf('=') + 1), out ulong parsedResidueMax)) if (Utf8CliParser.TryParseUInt64(argument.AsSpan("--residue-max-k=".Length), out ulong parsedResidueMax))
{ {
residueKMax = parsedResidueMax; residueKMax = parsedResidueMax;
} }
@@ -301,7 +302,7 @@ internal readonly struct CliArguments
if (argument.StartsWith("--mersenne-device=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--mersenne-device=", StringComparison.OrdinalIgnoreCase))
{ {
useMersenneOnGpu = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase); useMersenneOnGpu = !argument.AsSpan("--mersenne-device=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
continue; continue;
} }
@@ -319,7 +320,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(argument.IndexOf('=') + 1), out ulong parsedLimit)) if (Utf8CliParser.TryParseUInt64(argument.AsSpan("--order-warmup-limit=".Length), out ulong parsedLimit))
{ {
orderWarmupLimitOverride = parsedLimit; orderWarmupLimitOverride = parsedLimit;
} }
@@ -335,14 +336,14 @@ internal readonly struct CliArguments
if (argument.StartsWith("--ntt=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--ntt=", StringComparison.OrdinalIgnoreCase))
{ {
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1); ReadOnlySpan<char> value = argument.AsSpan("--ntt=".Length);
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(argument.IndexOf('=') + 1); ReadOnlySpan<char> value = argument.AsSpan("--mod-reduction=".Length);
if (value.Equals("mont64", StringComparison.OrdinalIgnoreCase)) if (value.Equals("mont64", StringComparison.OrdinalIgnoreCase))
{ {
modReductionMode = ModReductionMode.Mont64; modReductionMode = ModReductionMode.Mont64;
@@ -365,25 +366,25 @@ internal readonly struct CliArguments
if (argument.StartsWith("--primes-device=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--primes-device=", StringComparison.OrdinalIgnoreCase))
{ {
forcePrimeKernelsOnCpu = argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase); forcePrimeKernelsOnCpu = argument.AsSpan("--primes-device=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
continue; continue;
} }
if (argument.StartsWith("--order-device=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--order-device=", StringComparison.OrdinalIgnoreCase))
{ {
useOrderOnGpu = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase); useOrderOnGpu = !argument.AsSpan("--order-device=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
continue; continue;
} }
if (argument.StartsWith("--rle-blacklist=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--rle-blacklist=", StringComparison.OrdinalIgnoreCase))
{ {
rleBlacklistPath = argument[(argument.IndexOf('=') + 1)..]; rleBlacklistPath = argument["--rle-blacklist=".Length..];
continue; continue;
} }
if (argument.StartsWith("--rle-hard-max=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--rle-hard-max=", StringComparison.OrdinalIgnoreCase))
{ {
if (Utf8CliParser.TryParseUInt64(argument.AsSpan(argument.IndexOf('=') + 1), out ulong parsedMax)) if (Utf8CliParser.TryParseUInt64(argument.AsSpan("--rle-hard-max=".Length), out ulong parsedMax))
{ {
rleHardMaxP = parsedMax; rleHardMaxP = parsedMax;
} }
@@ -393,14 +394,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(argument.IndexOf('=') + 1); ReadOnlySpan<char> value = argument.AsSpan("--rle-only-last7=".Length);
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(argument.IndexOf('=') + 1), out double zeroFraction)) if (Utf8CliParser.TryParseDouble(argument.AsSpan("--zero-hard=".Length), out double zeroFraction))
{ {
zeroFractionHard = zeroFraction; zeroFractionHard = zeroFraction;
} }
@@ -410,7 +411,7 @@ internal readonly struct CliArguments
if (argument.StartsWith("--zero-conj=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--zero-conj=", StringComparison.OrdinalIgnoreCase))
{ {
ReadOnlySpan<char> value = argument.AsSpan(argument.IndexOf('=') + 1); ReadOnlySpan<char> value = argument.AsSpan("--zero-conj=".Length);
int colon = value.IndexOf(':'); int colon = value.IndexOf(':');
if (colon > 0) if (colon > 0)
{ {
@@ -427,87 +428,97 @@ internal readonly struct CliArguments
if (argument.StartsWith("--lucas=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--lucas=", StringComparison.OrdinalIgnoreCase))
{ {
useMersenneOnGpu = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase); useMersenneOnGpu = !argument.AsSpan("--lucas=".Length).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(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase); forcePrimeKernelsOnCpu = argument.AsSpan("--primes=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
continue;
}
if (argument.StartsWith("--gpu-kernels=", StringComparison.OrdinalIgnoreCase))
{
forcePrimeKernelsOnCpu = argument.AsSpan("--gpu-kernels=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
continue;
}
if (argument.StartsWith("--accelerator=", StringComparison.OrdinalIgnoreCase))
{
forcePrimeKernelsOnCpu = argument.AsSpan("--accelerator=".Length).Equals("cpu", StringComparison.OrdinalIgnoreCase);
continue; continue;
} }
if (argument.StartsWith("--results-dir=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--results-dir=", StringComparison.OrdinalIgnoreCase))
{ {
resultsDirectory = argument[(argument.IndexOf('=') + 1)..]; resultsDirectory = argument["--results-dir=".Length..];
continue; continue;
} }
if (argument.StartsWith("--results-prefix=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--results-prefix=", StringComparison.OrdinalIgnoreCase))
{ {
resultsPrefix = argument[(argument.IndexOf('=') + 1)..]; resultsPrefix = argument["--results-prefix=".Length..];
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(argument.IndexOf('=') + 1))); gpuPrimeThreads = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--gpu-prime-threads=".Length)));
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(argument.IndexOf('=') + 1))); gpuPrimeBatch = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--gpu-prime-batch=".Length)));
continue; continue;
} }
if (argument.StartsWith("--ll-slice=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--ll-slice=", StringComparison.OrdinalIgnoreCase))
{ {
sliceSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1))); sliceSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--ll-slice=".Length)));
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(argument.IndexOf('=') + 1))); scanBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--gpu-scan-batch=".Length)));
continue; continue;
} }
if (argument.StartsWith("--block-size=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--block-size=", StringComparison.OrdinalIgnoreCase))
{ {
blockSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan(argument.IndexOf('=') + 1))); blockSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--block-size=".Length)));
continue; continue;
} }
if (argument.StartsWith("--filter-p=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--filter-p=", StringComparison.OrdinalIgnoreCase))
{ {
filterFile = argument[(argument.IndexOf('=') + 1)..]; filterFile = argument["--filter-p=".Length..];
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(argument.IndexOf('=') + 1))); writeBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--write-batch-size=".Length)));
continue; continue;
} }
if (argument.StartsWith("--divisor-cycles=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--divisor-cycles=", StringComparison.OrdinalIgnoreCase))
{ {
cyclesPath = argument[(argument.IndexOf('=') + 1)..]; cyclesPath = argument["--divisor-cycles=".Length..];
continue; continue;
} }
if (argument.StartsWith("--divisor-cycles-device=", StringComparison.OrdinalIgnoreCase)) if (argument.StartsWith("--divisor-cycles-device=", StringComparison.OrdinalIgnoreCase))
{ {
useGpuCycles = !argument.AsSpan(argument.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase); useGpuCycles = !argument.AsSpan("--divisor-cycles-device=".Length).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(argument.IndexOf('=') + 1))); cyclesBatchSize = Math.Max(1, Utf8CliParser.ParseInt32(argument.AsSpan("--divisor-cycles-batch=".Length)));
continue; continue;
} }
@@ -14,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> span; ReadOnlySpan<char> trimmed;
while ((line = reader.ReadLine()) is not null) while ((line = reader.ReadLine()) is not null)
{ {
if (string.IsNullOrWhiteSpace(line)) if (string.IsNullOrWhiteSpace(line))
@@ -22,30 +22,60 @@ internal static class CalculationResultsFile
continue; continue;
} }
span = line.AsSpan().Trim(); trimmed = line.AsSpan().Trim();
if (span.IsEmpty || span[0] == '#') if (trimmed.IsEmpty || trimmed[0] == '#')
{ {
continue; continue;
} }
int length = trimmed.Length;
int index = 0; int index = 0;
while (index < span.Length) if (!maxPrimeConfigured)
{ {
if (!char.IsDigit(span[index])) while (index < length)
{
char current = trimmed[index];
uint digit = (uint)(current - '0');
if (digit > 9U)
{ {
index++; index++;
continue; continue;
} }
int start = index; int start = index++;
index++; while (index < length && (uint)(trimmed[index] - '0') <= 9U)
while (index < span.Length && char.IsDigit(span[index]))
{ {
index++; index++;
} }
if (Utf8CliParser.TryParseUInt64(span[start..index], out ulong parsed)) if (Utf8CliParser.TryParseUInt64(trimmed[start..index], out ulong parsed))
{ {
if (!maxPrimeConfigured || (UInt128)parsed <= maxPrimeLimit) candidates.Add(parsed);
}
}
}
else
{
UInt128 limit = maxPrimeLimit;
while (index < length)
{
char current = trimmed[index];
uint digit = (uint)(current - '0');
if (digit > 9U)
{
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);
} }
@@ -56,6 +86,7 @@ internal static class CalculationResultsFile
} }
} }
} }
}
return candidates; return candidates;
} }
+17 -24
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,6 +28,8 @@ 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
@@ -49,18 +51,22 @@ internal static class Program
NttGpuMath.GpuTransformBackend = _cliArguments.NttBackend; NttGpuMath.GpuTransformBackend = _cliArguments.NttBackend;
NttGpuMath.ReductionMode = _cliArguments.ModReductionMode; NttGpuMath.ReductionMode = _cliArguments.ModReductionMode;
GpuContextPool.ForceCpu = _cliArguments.ForcePrimeKernelsOnCpu; _runPrimesOnCpu = _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 = Math.Max(1, _cliArguments.GpuPrimeThreads);
UnboundedTaskScheduler.ConfigureThreadCount(threadCount); UnboundedTaskScheduler.ConfigureThreadCount(threadCount);
PrimeTester.GpuBatchSize = gpuPrimeBatch;
GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads);
GpuContextPool.WarmUpPool(threadCount);
PrimeTester.WarmUpGpuKernels(gpuPrimeThreads);
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;
@@ -104,14 +110,7 @@ internal static class Program
testPrimeCandidateLimit = Math.Max(1, threadCount) * 3; testPrimeCandidateLimit = Math.Max(1, threadCount) * 3;
} }
if (useByDivisor) _byDivisorStartPrime = useByDivisor && startPrimeProvided ? currentP : 0UL;
{
_byDivisorStartPrime = startPrimeProvided ? currentP : 0UL;
}
else
{
_byDivisorStartPrime = 0UL;
}
if (useByDivisor && !testMode && string.IsNullOrEmpty(filterFile)) if (useByDivisor && !testMode && string.IsNullOrEmpty(filterFile))
{ {
@@ -119,9 +118,6 @@ 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)
@@ -177,13 +173,10 @@ internal static class Program
Console.WriteLine("Divisor cycles are ready"); Console.WriteLine("Divisor cycles are ready");
if (useByDivisor) blockSize = useByDivisor ? 1 : blockSize;
{
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)
@@ -266,7 +259,7 @@ internal static class Program
orderWarmupLimitOverride ?? 5_000_000UL, orderWarmupLimitOverride ?? 5_000_000UL,
NttGpuMath.ReductionMode, NttGpuMath.ReductionMode,
mersenneOnGpu ? "gpu" : "cpu", mersenneOnGpu ? "gpu" : "cpu",
GpuContextPool.ForceCpu ? "cpu" : "gpu", _runPrimesOnCpu ? "cpu" : "gpu",
orderOnGpu ? "gpu" : "cpu"); orderOnGpu ? "gpu" : "cpu");
if (!string.IsNullOrEmpty(_cliArguments.ResultsPrefix)) if (!string.IsNullOrEmpty(_cliArguments.ResultsPrefix))
@@ -631,9 +624,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 (!(GpuContextPool.ForceCpu if (!(_runPrimesOnCpu
? PrimeTesters.Value!.IsPrime(p, CancellationToken.None) ? PrimeTester.IsPrime(p)
: PrimeTesters.Value!.IsPrimeGpu(p, CancellationToken.None))) : PrimeTester.IsPrimeGpu(p)))
{ {
_lastCompositeP = true; _lastCompositeP = true;
return false; return false;
@@ -9,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();
} // }
} }
@@ -121,7 +121,7 @@ public class GpuKernelCompilationTests
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<byte>> PrimeTesterSmallPrimeKernel = private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, 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 =
@@ -167,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)
{ {
@@ -336,7 +336,7 @@ public class GpuKernelCompilationTests
private static void CompilePrimeTesterSmallPrimeKernel(Accelerator accelerator) private static void CompilePrimeTesterSmallPrimeKernel(Accelerator accelerator)
{ {
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>>(PrimeTesterSmallPrimeKernel); var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterSmallPrimeKernel);
_ = kernel; _ = kernel;
} }
@@ -18,22 +18,33 @@ public class GpuPrimeLimiterTests
var startedSecond = new TaskCompletionSource<bool>(); var startedSecond = new TaskCompletionSource<bool>();
var sw = new Stopwatch(); var sw = new Stopwatch();
using var first = GpuPrimeWorkLimiter.Acquire(); GpuPrimeWorkLimiter.Acquire();
sw.Start(); sw.Start();
var secondTask = Task.Run(() => var secondTask = Task.Run(() =>
{ {
using var second = GpuPrimeWorkLimiter.Acquire(); 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();
}
// Release first after short delay, allowing second to continue. finally
// (Dispose at scope end, but we want to end now.) {
first.Dispose(); // Release first after the initial wait so the second task can proceed.
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().IsMersennePrime(p).Should().Be(expected); new MersenneNumberLucasLehmerGpuTester().IsPrime(p, true).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.IsMersennePrime(exponent).Should().BeFalse(); tester.IsPrime(exponent, true).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.IsMersennePrime(exponent).Should().BeFalse(); tester.IsPrime(exponent, true).Should().BeFalse();
} }
[Theory] [Theory]
@@ -10,19 +10,17 @@ public class PrimeTesterGpuTests
[Fact] [Fact]
public void IsPrimeGpu_accepts_known_primes() public void IsPrimeGpu_accepts_known_primes()
{ {
GpuContextPool.ForceCpu = false;
try 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)
{ {
tester.IsPrimeGpu(prime, CancellationToken.None).Should().BeTrue(); PrimeTester.IsPrimeGpu(prime).Should().BeTrue();
} }
} }
finally finally
{ {
GpuContextPool.ForceCpu = false;
GpuContextPool.DisposeAll(); GpuContextPool.DisposeAll();
} }
} }
@@ -30,19 +28,17 @@ public class PrimeTesterGpuTests
[Fact] [Fact]
public void IsPrimeGpu_rejects_composites() public void IsPrimeGpu_rejects_composites()
{ {
GpuContextPool.ForceCpu = false;
try 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)
{ {
tester.IsPrimeGpu(composite, CancellationToken.None).Should().BeFalse(); PrimeTester.IsPrimeGpu(composite).Should().BeFalse();
} }
} }
finally finally
{ {
GpuContextPool.ForceCpu = false;
GpuContextPool.DisposeAll(); GpuContextPool.DisposeAll();
} }
} }
+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,19 +17,31 @@ public class GpuWorkLimiterTests
var startedSecond = new TaskCompletionSource<bool>(); var startedSecond = new TaskCompletionSource<bool>();
var sw = new Stopwatch(); var sw = new Stopwatch();
using var first = GpuWorkLimiter.Acquire(); var first = GpuWorkLimiter.Acquire();
sw.Start(); sw.Start();
var secondTask = Task.Run(() => var secondTask = Task.Run(() =>
{ {
using var second = GpuWorkLimiter.Acquire(); 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();
@@ -0,0 +1,239 @@
using System;
using System.Numerics;
using System.Collections.Generic;
using PerfectNumbers.Core;
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);
}
}
@@ -30,7 +30,9 @@ public class MersenneNumberDivisorCpuTesterTests
var tester = new MersenneNumberDivisorByDivisorCpuTester(); var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(13UL); tester.ConfigureFromMaxPrime(13UL);
using var session = tester.CreateDivisorSession(); 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];
@@ -43,6 +45,11 @@ 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.Dispose();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -51,7 +58,9 @@ public class MersenneNumberDivisorCpuTesterTests
var tester = new MersenneNumberDivisorByDivisorCpuTester(); var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(43UL); tester.ConfigureFromMaxPrime(43UL);
using var session = tester.CreateDivisorSession(); 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];
@@ -61,6 +70,11 @@ 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.Dispose();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -69,7 +83,9 @@ public class MersenneNumberDivisorCpuTesterTests
var tester = new MersenneNumberDivisorByDivisorCpuTester(); var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(61UL); tester.ConfigureFromMaxPrime(61UL);
using var session = tester.CreateDivisorSession(); 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];
@@ -79,5 +95,10 @@ 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.Dispose();
}
}
} }
@@ -149,7 +149,9 @@ public class MersenneNumberDivisorGpuTesterTests
var tester = new MersenneNumberDivisorByDivisorGpuTester(); var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(43UL); tester.ConfigureFromMaxPrime(43UL);
using var session = tester.CreateDivisorSession(); 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];
@@ -157,6 +159,11 @@ public class MersenneNumberDivisorGpuTesterTests
hits.Should().ContainInOrder(new byte[] { 0, 1, 0 }); hits.Should().ContainInOrder(new byte[] { 0, 1, 0 });
} }
finally
{
session.Dispose();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -165,7 +172,9 @@ public class MersenneNumberDivisorGpuTesterTests
var tester = new MersenneNumberDivisorByDivisorGpuTester(); var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(43UL); tester.ConfigureFromMaxPrime(43UL);
using var session = tester.CreateDivisorSession(); 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];
@@ -178,6 +187,11 @@ 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.Dispose();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -190,7 +204,9 @@ public class MersenneNumberDivisorGpuTesterTests
tester.ConfigureFromMaxPrime(47UL); tester.ConfigureFromMaxPrime(47UL);
using var session = tester.CreateDivisorSession(); 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];
@@ -199,6 +215,11 @@ 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.Dispose();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -210,7 +231,9 @@ public class MersenneNumberDivisorGpuTesterTests
}; };
tester.ConfigureFromMaxPrime(43UL); tester.ConfigureFromMaxPrime(43UL);
using var session = tester.CreateDivisorSession(); 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];
@@ -221,6 +244,11 @@ 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.Dispose();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -232,7 +260,9 @@ public class MersenneNumberDivisorGpuTesterTests
}; };
tester.ConfigureFromMaxPrime(61UL); tester.ConfigureFromMaxPrime(61UL);
using var session = tester.CreateDivisorSession(); 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];
@@ -243,6 +273,11 @@ 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.Dispose();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -297,7 +332,9 @@ public class MersenneNumberDivisorGpuTesterTests
tester.ConfigureFromMaxPrime(47UL); tester.ConfigureFromMaxPrime(47UL);
using var session = tester.CreateDivisorSession(); 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];
@@ -306,5 +343,10 @@ 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.Dispose();
}
}
} }
@@ -17,12 +17,29 @@ 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();
tester.IsPrime(n, CancellationToken.None).Should().Be(expected); PrimeTester.IsPrime(n).Should().Be(expected);
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(139UL, true)]
[InlineData(189UL, false)]
[InlineData(193UL, true)]
[InlineData(279UL, false)]
public void IsPrimeGpu_uses_residue_specific_tables(ulong n, bool expected)
{
var tester = new PrimeTester();
PrimeTester.IsPrimeGpu(n).Should().Be(expected);
} }
[Fact] [Fact]
@@ -30,20 +47,14 @@ 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();
GpuContextPool.ForceCpu = true; PrimeTester.IsPrime(137UL).Should().BeTrue();
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;
@@ -53,7 +64,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] = testers.Value!.IsPrimeGpu(primes[i], CancellationToken.None); results[i] = PrimeTester.IsPrimeGpu(primes[i]);
}); });
results.Should().AllBeEquivalentTo(true); results.Should().AllBeEquivalentTo(true);
@@ -62,7 +73,7 @@ public class PrimeTesterIsPrimeTests
{ {
foreach (ulong prime in primes) foreach (ulong prime in primes)
{ {
testers.Value!.IsPrimeGpu(prime, CancellationToken.None).Should().BeTrue(); PrimeTester.IsPrimeGpu(prime).Should().BeTrue();
} }
}); });
} }
@@ -32,14 +32,39 @@ 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++)
{ {
@@ -58,6 +83,64 @@ 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,11 +2,8 @@ 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(
@@ -67,12 +64,6 @@ internal sealed class MersenneCpuDivisorScanSession : IMersenneNumberDivisorByDi
public void Dispose() public void Dispose()
{ {
if (_disposed)
{
throw new ObjectDisposedException(nameof(MersenneCpuDivisorScanSession), "Divisor scan session disposed twice.");
}
_disposed = true;
ThreadStaticPools.ReturnMersenneCpuDivisorSession(this); ThreadStaticPools.ReturnMersenneCpuDivisorSession(this);
} }
} }
@@ -7,10 +7,7 @@ namespace PerfectNumbers.Core.Cpu;
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
{ {
private readonly object _sync = new();
private ulong _divisorLimit; private ulong _divisorLimit;
private ulong _lastStatusDivisor;
private int _batchSize = 1_024; private int _batchSize = 1_024;
[ThreadStatic] [ThreadStatic]
@@ -32,7 +29,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
public void ConfigureFromMaxPrime(ulong maxPrime) public void ConfigureFromMaxPrime(ulong maxPrime)
{ {
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime); _divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
_lastStatusDivisor = 0UL;
} }
public ulong DivisorLimit public ulong DivisorLimit
@@ -60,22 +56,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
// return true; // return true;
// } // }
ulong processedCount;
bool processedAll; bool processedAll;
bool composite = CheckDivisors( bool composite = CheckDivisors(
prime, prime,
allowedMax, allowedMax,
out processedAll, out processedAll);
out processedCount);
if (processedCount > 0UL)
{
lock (_sync)
{
UpdateStatusUnsafe(processedCount);
}
}
if (composite) if (composite)
{ {
@@ -111,10 +97,8 @@ 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 processedCount)
{ {
processedCount = 0UL;
processedAll = false; 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.
@@ -151,44 +135,78 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
ulong stepHigh = step.High; ulong stepHigh = step.High;
ulong stepLow = step.Low; ulong stepLow = step.Low;
byte step10 = (byte)((((stepHigh % 10UL) * 6UL) + (stepLow % 10UL)) % 10UL); ulong limitHigh = limit.High;
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;
bool lastIsSeven = lastDigit == LastDigit.Seven; ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit);
const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9);
const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 9); 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);
}
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);
while (divisor.CompareTo(limit) <= 0) while (divisor.CompareTo(limit) <= 0)
{ {
ulong candidate = divisor.Low; bool passesSmallModuli = remainder3 != 0 && remainder7 != 0 && remainder11 != 0;
processedCount++; if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
ushort decimalMask = lastIsSeven ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise;
bool admissible = ((decimalMask >> remainder10) & 1) != 0;
if (admissible
&& (remainder8 == 1 || remainder8 == 7)
&& remainder3 != 0
&& remainder5 != 0
&& remainder7 != 0
&& remainder11 != 0)
{ {
ulong candidate = divisor.Low;
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.
@@ -233,53 +251,206 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
} }
divisor.Add(step); divisor.Add(step);
remainder10 = AddMod(remainder10, step10, (byte)10); remainder10 = AddMod10(remainder10, step10);
remainder8 = AddMod(remainder8, step8, (byte)8); remainder8 = AddMod8(remainder8, step8);
remainder5 = AddMod(remainder5, step5, (byte)5); remainder3 = AddMod3(remainder3, step3);
remainder3 = AddMod(remainder3, step3, (byte)3); remainder7 = AddMod7(remainder7, step7);
remainder7 = AddMod(remainder7, step7, (byte)7); remainder11 = AddMod11(remainder11, step11);
remainder11 = AddMod(remainder11, step11, (byte)11); }
processedAll = true;
return false;
} }
processedAll = divisor.CompareTo(limit) > 0; 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)
{
processedAll = false;
bool canAdvance = step <= limit;
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(
divisor,
prime,
divisorData,
out ulong computedCycle,
out bool primeOrderFailed) || computedCycle == 0UL)
{
divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
divisor,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
}
else
{
divisorCycle = computedCycle;
}
if (divisorCycle == prime)
{
processedAll = true;
return true;
}
if (divisorCycle == 0UL)
{
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
}
}
}
processedAll = true;
return false; return false;
} }
if (divisor > limit)
{
processedAll = true;
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(
divisor,
prime,
divisorData,
out ulong computedCycle,
out bool primeOrderFailed) || computedCycle == 0UL)
{
divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
divisor,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
}
else
{
divisorCycle = computedCycle;
}
if (divisorCycle == prime)
{
processedAll = true;
return true;
}
if (divisorCycle == 0UL)
{
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
}
}
remainingIterations--;
if (remainingIterations == 0UL)
{
processedAll = true;
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);
return residue == 1UL ? (byte)1 : (byte)0; return residue == 1UL ? (byte)1 : (byte)0;
} }
private static byte AddMod(byte value, byte delta, byte modulus) [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod3(byte value, byte delta)
{ {
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; return (byte)sum;
} }
private void UpdateStatusUnsafe(ulong processedCount)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod7(byte value, byte delta)
{ {
if (processedCount == 0UL) const int Modulus = 7;
int sum = value + delta;
if (sum >= Modulus)
{ {
return; sum -= Modulus;
} }
ulong interval = PerfectNumberConstants.ConsoleInterval; return (byte)sum;
if (interval == 0UL)
{
_lastStatusDivisor = 0UL;
return;
} }
ulong total = _lastStatusDivisor + processedCount; [MethodImpl(MethodImplOptions.AggressiveInlining)]
// TODO: Replace this modulo with the ring-buffer style counter (subtract loop) used in the fast CLI private static byte AddMod10(byte value, byte delta)
// status benchmarks so we avoid `%` in this hot loop while still wrapping progress correctly. {
_lastStatusDivisor = total % interval; const int Modulus = 10;
int sum = value + delta;
if (sum >= Modulus)
{
sum -= Modulus;
}
return (byte)sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod11(byte value, byte delta)
{
const int Modulus = 11;
int sum = value + delta;
if (sum >= Modulus)
{
sum -= Modulus;
}
return (byte)sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod8(byte value, byte delta)
{
return (byte)((value + delta) & 7);
} }
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime) private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime)
-2
View File
@@ -227,7 +227,6 @@ public sealed class DivisorCycleCache
{ {
int length = divisors.Length; int length = divisors.Length;
var gpuLease = GpuKernelPool.GetKernel(useGpuOrder: true); var gpuLease = GpuKernelPool.GetKernel(useGpuOrder: true);
var execution = gpuLease.EnterExecutionScope();
Accelerator accelerator = gpuLease.Accelerator; Accelerator accelerator = gpuLease.Accelerator;
var kernel = _gpuKernelCache.GetOrAdd(accelerator, LoadKernel); var kernel = _gpuKernelCache.GetOrAdd(accelerator, LoadKernel);
@@ -309,7 +308,6 @@ public sealed class DivisorCycleCache
ArrayPool<byte>.Shared.Return(rentedStatus!, clearArray: false); ArrayPool<byte>.Shared.Return(rentedStatus!, clearArray: false);
} }
execution.Dispose();
gpuLease.Dispose(); gpuLease.Dispose();
} }
+255 -16
View File
@@ -1,38 +1,277 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core; 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 bool IsValidDivisor( public static ushort GetDecimalMask(LastDigit lastDigit)
byte remainder10,
byte remainder8,
byte remainder3,
byte remainder5,
byte remainder7,
byte remainder11,
LastDigit lastDigit)
{ {
ushort decimalMask = lastDigit == LastDigit.Seven ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise; return lastDigit switch
if (((decimalMask >> remainder10) & 1) == 0)
{ {
return false; LastDigit.Seven => DecimalMaskWhenLastIsSeven,
LastDigit.Three => DecimalMaskWhenLastIsThree,
LastDigit.Nine => DecimalMaskWhenLastIsNine,
_ => DecimalMaskOtherwise,
};
} }
if (remainder8 != 1 && remainder8 != 7) 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)
{ {
return false; 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 (remainder3 == 0 || remainder5 == 0 || remainder7 == 0 || remainder11 == 0) if (candidate % p == 0U)
{ {
return false; isPrime = false;
break;
}
} }
return true; 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)]
private static LastDigit GetLastDigit(uint prime)
{
return (prime % 10U) switch
{
1U => LastDigit.One,
3U => LastDigit.Three,
5U => LastDigit.Five,
7U => LastDigit.Seven,
9U => LastDigit.Nine,
_ => throw new ArgumentOutOfRangeException(nameof(prime)),
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsAllowedForLastOne(uint prime, LastDigit lastDigit)
{
// TODO: Swap the `% 10` usage for ULongExtensions.Mod10 so the hot classification path
// reuses the benchmarked residue helper instead of repeated divisions.
return lastDigit switch
{
LastDigit.One or LastDigit.Three or LastDigit.Nine => true,
LastDigit.Seven => prime == 7U,
_ => prime == 11U,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsAllowedForLastSeven(uint prime, LastDigit lastDigit)
{
// TODO: Route this `% 10` classification through ULongExtensions.Mod10 to match the faster
// residue helper used elsewhere in the scanner.
return lastDigit switch
{
LastDigit.Three or LastDigit.Seven or LastDigit.Nine => true,
_ => prime == 11U,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsAllowedForLastThree(uint prime, LastDigit lastDigit)
{
// TODO: Replace the `% 10` residue classification with ULongExtensions.Mod10 once the helper
// migrates to UInt32 so we can reuse the optimized decimal residue path everywhere.
return lastDigit switch
{
LastDigit.Three or LastDigit.Seven => true,
LastDigit.One => prime == 11U,
LastDigit.Nine => prime == 19U,
_ => false,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsAllowedForLastNine(uint prime, LastDigit lastDigit)
{
// TODO: Replace the `% 10` residue classification with ULongExtensions.Mod10 when the helper
// supports UInt32 inputs so the residue tables share the optimized decimal path.
return lastDigit switch
{
LastDigit.Three or LastDigit.Seven or LastDigit.Nine => true,
LastDigit.One => prime == 11U,
_ => false,
};
} }
} }
@@ -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.MontgomeryMultiply(multiplier, _modulus, _nPrime); _currentMontgomery = _currentMontgomery.MontgomeryMultiplyCpu(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.MontgomeryMultiply(multiplier, _modulus, _nPrime); _currentMontgomery = _currentMontgomery.MontgomeryMultiplyCpu(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.MontgomeryMultiply(montgomeryDelta, _modulus, _nPrime); _currentMontgomery = _currentMontgomery.MontgomeryMultiplyCpu(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.MontgomeryMultiply(1UL, _modulus, _nPrime); private ulong ReduceCurrent() => _currentMontgomery.MontgomeryMultiplyCpu(1UL, _modulus, _nPrime);
} }
+44 -80
View File
@@ -6,26 +6,20 @@ namespace PerfectNumbers.Core.Gpu;
public static class GpuContextPool public static class GpuContextPool
{ {
private static readonly bool PoolingEnabled = true;
// The pool intentionally skips pre-loading ProcessEightBitWindows kernels so accelerator initialization // 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. // 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.) // Default device preference for generic GPU kernels (prime scans, NTT, etc.)
public static bool ForceCpu { get; set; } = false;
internal sealed class PooledContext internal sealed class PooledContext
{ {
private bool disposed; public readonly Context Context;
public readonly Accelerator Accelerator;
public Context Context { get; } public PooledContext(KernelContainer kernels)
public Accelerator Accelerator { get; }
public bool IsCpu { get; }
public object ExecutionLock { get; } = new();
public PooledContext(bool preferCpu)
{ {
Context = Context.CreateDefault(); Context = SharedGpuContext.Context;
Accelerator = Context.GetPreferredDevice(preferCpu).CreateAccelerator(Context); Accelerator = SharedGpuContext.Accelerator;
IsCpu = Accelerator.AcceleratorType == AcceleratorType.CPU; GpuStaticTableInitializer.EnsureStaticTables(kernels, Accelerator);
kernels.Dispose();
// NOTE: Avoid loading/compiling any kernel here to prevent implicit // NOTE: Avoid loading/compiling any kernel here to prevent implicit
// CL stream/queue creation during accelerator construction. // CL stream/queue creation during accelerator construction.
// Some OpenCL drivers are fragile when a queue is created immediately // Some OpenCL drivers are fragile when a queue is created immediately
@@ -34,11 +28,6 @@ public static class GpuContextPool
public void Dispose() public void Dispose()
{ {
if (disposed)
{
return;
}
// Ensure all cached GPU buffers for this accelerator are released. // Ensure all cached GPU buffers for this accelerator are released.
NttGpuMath.ClearCaches(Accelerator); NttGpuMath.ClearCaches(Accelerator);
// Release PrimeTester GPU state for this accelerator (kernel/device primes) // Release PrimeTester GPU state for this accelerator (kernel/device primes)
@@ -46,117 +35,92 @@ public static class GpuContextPool
// after the accelerator is destroyed. // after the accelerator is destroyed.
PrimeTester.ClearGpuCaches(Accelerator); PrimeTester.ClearGpuCaches(Accelerator);
Accelerator.Dispose(); // These resources are shared between GPU leases
Context.Dispose(); // Accelerator.Dispose();
disposed = true; // Context.Dispose();
} }
} }
private static readonly ConcurrentQueue<PooledContext> CpuPool = new();
private static readonly ConcurrentQueue<PooledContext> GpuPool = new(); private static readonly ConcurrentQueue<PooledContext> GpuPool = new();
private static readonly object CreationLock = new(); private static int WarmedGpuContextCount;
public static GpuContextLease Rent()
{
return RentPreferred(ForceCpu);
}
// Allows callers to choose CPU/GPU per use-case, decoupled from ForceCpu. // Allows callers to choose CPU/GPU per use-case, decoupled from ForceCpu.
public static GpuContextLease RentPreferred(bool preferCpu) public static GpuContextLease Rent()
{ {
if (PoolingEnabled) if (GpuPool.TryDequeue(out var gpu))
{ {
if (preferCpu && CpuPool.TryDequeue(out var cpu)) return new GpuContextLease(gpu);
{
return new GpuContextLease(cpu, CreationLock);
} }
if (!preferCpu && GpuPool.TryDequeue(out var gpu)) // Create a new accelerator when the pool does not have one available.
{ return new GpuContextLease(new PooledContext(GpuKernelPool.GetKernels()));
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() public static void WarmUpPool(int threadCount)
{ {
if (!PoolingEnabled) if (threadCount <= WarmedGpuContextCount)
{ {
return; return;
} }
while (CpuPool.TryDequeue(out var cpu)) int toCreate = threadCount - WarmedGpuContextCount;
Console.WriteLine($"Warming up GPU context pool to {toCreate} contexts...");
var contexts = new PooledContext[toCreate];
var pool = GpuPool;
PooledContext context;
for (int i = 0; i < toCreate; i++)
{ {
cpu.Dispose(); KernelContainer kernels = GpuKernelPool.GetKernels();
context = new PooledContext(kernels);
contexts[i] = context;
pool.Enqueue(context);
} }
WarmedGpuContextCount = threadCount;
}
public static void DisposeAll()
{
while (GpuPool.TryDequeue(out var gpu)) while (GpuPool.TryDequeue(out var gpu))
{ {
gpu.Dispose(); gpu.Dispose();
} }
PrimeTester.DisposeGpuContexts(); PrimeTester.DisposeGpuContexts();
WarmedGpuContextCount = 0;
} }
private static void Return(PooledContext ctx) private static void Return(PooledContext ctx)
{
if (PoolingEnabled)
{ {
ctx.Accelerator.Synchronize(); ctx.Accelerator.Synchronize();
if (ctx.IsCpu)
{
CpuPool.Enqueue(ctx);
}
else
{
GpuPool.Enqueue(ctx); GpuPool.Enqueue(ctx);
} }
}
else
{
lock (CreationLock)
{
ctx.Dispose();
}
}
}
public struct GpuContextLease public struct GpuContextLease
{ {
private readonly PooledContext _ctx; private PooledContext? _ctx;
private bool disposed; private bool _disposed;
internal GpuContextLease(PooledContext ctx, object kernelInitLock) internal GpuContextLease(PooledContext ctx)
{ {
_ctx = ctx; _ctx = ctx;
KernelInitLock = kernelInitLock; _disposed = false;
} }
public Context Context => _ctx.Context; public Context Context => _ctx!.Context;
public Accelerator Accelerator => _ctx!.Accelerator;
public Accelerator Accelerator => _ctx.Accelerator;
public object KernelInitLock { get; }
public object ExecutionLock => _ctx.ExecutionLock;
public void Dispose() public void Dispose()
{ {
if (disposed) if (_disposed)
{ {
return; return;
} }
Return(_ctx); _disposed = true;
disposed = true; Return(_ctx!);
_ctx = null;
} }
} }
} }
+30 -78
View File
@@ -1,6 +1,8 @@
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core;
using static PerfectNumbers.Core.Gpu.GpuContextPool; using static PerfectNumbers.Core.Gpu.GpuContextPool;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -9,30 +11,25 @@ public sealed class GpuKernelLease
{ {
private static readonly ConcurrentQueue<GpuKernelLease> Pool = new(); private static readonly ConcurrentQueue<GpuKernelLease> Pool = new();
private IDisposable? _limiter;
private GpuContextLease? _gpu; private GpuContextLease? _gpu;
private KernelContainer? _kernels; public KernelContainer Kernels;
private AcceleratorStream? _stream; private AcceleratorStream? _stream;
private object? _executionLock;
private bool _disposed;
private GpuKernelLease() private GpuKernelLease(KernelContainer kernels)
{ {
Kernels = kernels;
} }
internal static GpuKernelLease Rent(IDisposable limiter, GpuContextLease gpu, KernelContainer kernels) internal static GpuKernelLease Rent(GpuContextLease gpu, KernelContainer kernels)
{ {
if (!Pool.TryDequeue(out var lease)) if (!Pool.TryDequeue(out var lease))
{ {
lease = new GpuKernelLease(); lease = new GpuKernelLease(kernels);
} }
lease._limiter = limiter;
lease._gpu = gpu; lease._gpu = gpu;
lease._kernels = kernels; lease.Kernels = kernels;
lease._stream = null; lease._stream = null;
lease._executionLock = gpu.ExecutionLock;
lease._disposed = false;
return lease; return lease;
} }
@@ -54,16 +51,12 @@ public sealed class GpuKernelLease
} }
} }
public ExecutionScope EnterExecutionScope() => new ExecutionScope(_executionLock);
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>> OrderKernel public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>> OrderKernel
{ {
get get
{ {
var accel = Accelerator; // avoid capturing 'this' in lambda var accel = Accelerator; // avoid capturing 'this' in lambda
lock (_gpu!.Value.KernelInitLock) return KernelContainer.InitOnce(ref Kernels!.Order, () =>
{
return KernelContainer.InitOnce(ref _kernels!.Order, () =>
{ {
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernels.OrderKernelScan); var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernels.OrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded); var kernel = KernelUtil.GetKernel(loaded);
@@ -71,16 +64,13 @@ public sealed class GpuKernelLease
}); });
} }
} }
}
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 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 get
{ {
var accel = Accelerator; var accel = Accelerator;
lock (_gpu!.Value.KernelInitLock) return KernelContainer.InitOnce(ref Kernels!.Pow2Mod, () =>
{
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 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); var kernel = KernelUtil.GetKernel(loaded);
@@ -88,16 +78,13 @@ public sealed class GpuKernelLease
}); });
} }
} }
}
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalKernel public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalKernel
{ {
get get
{ {
var accel = Accelerator; var accel = Accelerator;
lock (_gpu!.Value.KernelInitLock) return KernelContainer.InitOnce(ref Kernels!.Incremental, () =>
{
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 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); var kernel = KernelUtil.GetKernel(loaded);
@@ -105,16 +92,13 @@ public sealed class GpuKernelLease
}); });
} }
} }
}
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalOrderKernel public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalOrderKernel
{ {
get get
{ {
var accel = Accelerator; var accel = Accelerator;
lock (_gpu!.Value.KernelInitLock) return KernelContainer.InitOnce(ref Kernels!.IncrementalOrder, () =>
{
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 loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalOrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded); var kernel = KernelUtil.GetKernel(loaded);
@@ -122,16 +106,13 @@ public sealed class GpuKernelLease
}); });
} }
} }
}
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModOrderKernel public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModOrderKernel
{ {
get get
{ {
var accel = Accelerator; var accel = Accelerator;
lock (_gpu!.Value.KernelInitLock) return KernelContainer.InitOnce(ref Kernels!.Pow2ModOrder, () =>
{
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 loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModOrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded); var kernel = KernelUtil.GetKernel(loaded);
@@ -139,6 +120,19 @@ public sealed class GpuKernelLease
}); });
} }
} }
public Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ushort, Stride1D.Dense>> SpecialMaxKernel
{
get
{
var accel = Accelerator;
return KernelContainer.InitOnce(ref Kernels!.SpecialMax, () =>
{
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ushort, Stride1D.Dense>>(PrimeOrderGpuHeuristics.EvaluateSpecialMaxCandidatesKernel);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ushort, 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 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
@@ -146,9 +140,7 @@ public sealed class GpuKernelLease
get get
{ {
var accel = Accelerator; var accel = Accelerator;
lock (_gpu!.Value.KernelInitLock) return KernelContainer.InitOnce(ref Kernels!.SmallPrimeFactor, () =>
{
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 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); var kernel = KernelUtil.GetKernel(loaded);
@@ -156,16 +148,8 @@ public sealed class GpuKernelLease
}); });
} }
} }
}
private void Dispose(bool disposing) public void Dispose()
{
if (_disposed)
{
return;
}
if (disposing)
{ {
_stream?.Dispose(); _stream?.Dispose();
_stream = null; _stream = null;
@@ -173,41 +157,9 @@ public sealed class GpuKernelLease
_gpu?.Dispose(); _gpu?.Dispose();
_gpu = null; _gpu = null;
_executionLock = null; Kernels?.Dispose();
GpuPrimeWorkLimiter.Release();
_limiter?.Dispose();
_limiter = null;
}
_kernels = null;
_disposed = true;
Pool.Enqueue(this); 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);
}
}
}
} }
+78 -61
View File
@@ -1,3 +1,4 @@
using PerfectNumbers.Core;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
@@ -37,6 +38,7 @@ public sealed class KernelContainer
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2ModOrder; 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; 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;
public Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ushort, Stride1D.Dense>>? SpecialMax;
// Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length. // Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length.
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallCycles; public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallCycles;
@@ -46,6 +48,9 @@ public sealed class KernelContainer
public MemoryBuffer1D<int, Stride1D.Dense>? SmallPrimeFactorExponentSlots; public MemoryBuffer1D<int, Stride1D.Dense>? SmallPrimeFactorExponentSlots;
public MemoryBuffer1D<int, Stride1D.Dense>? SmallPrimeFactorCountSlot; public MemoryBuffer1D<int, Stride1D.Dense>? SmallPrimeFactorCountSlot;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorRemainingSlot; public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorRemainingSlot;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SpecialMaxFactors;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SpecialMaxCandidates;
public MemoryBuffer1D<ushort, Stride1D.Dense>? SpecialMaxResult;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastOne; public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastOne;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastOne; public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastOne;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastSeven; public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastSeven;
@@ -67,6 +72,8 @@ public sealed class KernelContainer
} }
return current; return current;
} }
public void Dispose() => GpuKernelPool.Return(this);
} }
public readonly struct ResiduePrimeViews( public readonly struct ResiduePrimeViews(
@@ -81,44 +88,29 @@ public readonly struct ResiduePrimeViews(
public readonly ArrayView1D<ulong, Stride1D.Dense> LastSevenPow2 = lastSevenPow2; public readonly ArrayView1D<ulong, Stride1D.Dense> LastSevenPow2 = lastSevenPow2;
} }
public readonly struct SmallPrimeFactorTables 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<uint, Stride1D.Dense> primes,
MemoryBuffer1D<ulong, Stride1D.Dense> squares, MemoryBuffer1D<ulong, Stride1D.Dense> squares,
int count) int count)
{ {
Primes = primes; public readonly MemoryBuffer1D<uint, Stride1D.Dense> Primes = primes;
Squares = squares; public readonly MemoryBuffer1D<ulong, Stride1D.Dense> Squares = squares;
Count = count; public readonly int Count = count;
}
public ArrayView1D<uint, Stride1D.Dense> PrimesView => Primes.View; public ArrayView1D<uint, Stride1D.Dense> PrimesView => Primes.View;
public ArrayView1D<ulong, Stride1D.Dense> SquaresView => Squares.View; public ArrayView1D<ulong, Stride1D.Dense> SquaresView => Squares.View;
} }
public readonly struct SmallPrimeFactorScratch 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<ulong, Stride1D.Dense> primeSlots,
MemoryBuffer1D<int, Stride1D.Dense> exponentSlots, MemoryBuffer1D<int, Stride1D.Dense> exponentSlots,
MemoryBuffer1D<int, Stride1D.Dense> countSlot, MemoryBuffer1D<int, Stride1D.Dense> countSlot,
MemoryBuffer1D<ulong, Stride1D.Dense> remainingSlot) MemoryBuffer1D<ulong, Stride1D.Dense> remainingSlot)
{ {
PrimeSlots = primeSlots; public readonly MemoryBuffer1D<ulong, Stride1D.Dense> PrimeSlots = primeSlots;
ExponentSlots = exponentSlots; public readonly MemoryBuffer1D<int, Stride1D.Dense> ExponentSlots = exponentSlots;
CountSlot = countSlot; public readonly MemoryBuffer1D<int, Stride1D.Dense> CountSlot = countSlot;
RemainingSlot = remainingSlot; public readonly MemoryBuffer1D<ulong, Stride1D.Dense> RemainingSlot = remainingSlot;
}
public void Clear() public void Clear()
{ {
@@ -129,44 +121,55 @@ public readonly struct SmallPrimeFactorScratch
} }
} }
public readonly struct SpecialMaxScratch(
MemoryBuffer1D<ulong, Stride1D.Dense> factorValues,
MemoryBuffer1D<ulong, Stride1D.Dense> candidateValues,
MemoryBuffer1D<ushort, Stride1D.Dense> resultSlot)
{
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> FactorValues = factorValues;
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> CandidateValues = candidateValues;
public readonly MemoryBuffer1D<ushort, Stride1D.Dense> ResultSlot = resultSlot;
public ArrayView1D<ulong, Stride1D.Dense> FactorsView => FactorValues.View;
public ArrayView1D<ulong, Stride1D.Dense> CandidatesView => CandidateValues.View;
public ArrayView1D<ushort, Stride1D.Dense> ResultView => ResultSlot.View;
}
public class GpuKernelPool public class GpuKernelPool
{ {
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 readonly ConcurrentQueue<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 GetKernels(Accelerator accelerator) public static KernelContainer GetKernels() => KernelCache.TryDequeue(out var container)
? container
: new KernelContainer();
public static void Return(KernelContainer container) => KernelCache.Enqueue(container);
internal static void PreloadStaticTables(KernelContainer kernels, Accelerator accelerator)
{ {
return KernelCache.GetOrAdd(accelerator, _ => new KernelContainer()); EnsureSmallCyclesOnDevice(kernels, accelerator);
EnsureSmallPrimesOnDevice(kernels, accelerator);
EnsureSmallPrimeFactorTables(kernels, accelerator);
} }
// 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).
public static ArrayView1D<ulong, Stride1D.Dense> EnsureSmallCyclesOnDevice(Accelerator accelerator) public static ArrayView1D<ulong, Stride1D.Dense> EnsureSmallCyclesOnDevice(KernelContainer kernels, Accelerator accelerator)
{ {
var kernels = GetKernels(accelerator);
if (kernels.SmallCycles is { } buffer) if (kernels.SmallCycles is { } buffer)
{ {
return buffer.View; return buffer.View;
} }
// Ensure single upload per accelerator even if multiple threads race here.
lock (kernels) // TODO: Remove this lock by pre-uploading the immutable small-cycle snapshot during initialization; once no mutation happens at runtime, the pool must expose a simple reference without synchronization.
{
if (kernels.SmallCycles is { } existing)
{
return existing.View;
}
var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); // TODO: Preload this device buffer during startup and keep it immutable so we can delete the lock above in favor of the preloaded snapshot. var 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); var device = accelerator.Allocate1D<ulong>(host.Length);
device.View.CopyFromCPU(host); device.View.CopyFromCPU(host);
kernels.SmallCycles = device; kernels.SmallCycles = device;
return device.View; return device.View;
} }
}
public static ResiduePrimeViews EnsureSmallPrimesOnDevice(Accelerator accelerator) public static ResiduePrimeViews EnsureSmallPrimesOnDevice(KernelContainer kernels, Accelerator accelerator)
{ {
var kernels = GetKernels(accelerator);
if (kernels.SmallPrimesLastOne is { } lastOne && if (kernels.SmallPrimesLastOne is { } lastOne &&
kernels.SmallPrimesLastSeven is { } lastSeven && kernels.SmallPrimesLastSeven is { } lastSeven &&
kernels.SmallPrimesPow2LastOne is { } lastOnePow2 && kernels.SmallPrimesPow2LastOne is { } lastOnePow2 &&
@@ -175,8 +178,6 @@ public class GpuKernelPool
return new ResiduePrimeViews(lastOne.View, lastSeven.View, lastOnePow2.View, lastSevenPow2.View); return new ResiduePrimeViews(lastOne.View, lastSeven.View, lastOnePow2.View, lastSevenPow2.View);
} }
lock (kernels) // TODO: Inline these small-prime uploads into startup initialization alongside the small-cycle snapshot so we can drop runtime locking and keep the GPU pool free of synchronization.
{
if (kernels.SmallPrimesLastOne is { } existingLastOne && if (kernels.SmallPrimesLastOne is { } existingLastOne &&
kernels.SmallPrimesLastSeven is { } existingLastSeven && kernels.SmallPrimesLastSeven is { } existingLastSeven &&
kernels.SmallPrimesPow2LastOne is { } existingLastOnePow2 && kernels.SmallPrimesPow2LastOne is { } existingLastOnePow2 &&
@@ -206,18 +207,14 @@ public class GpuKernelPool
return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View); return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View);
} }
}
public static SmallPrimeFactorTables EnsureSmallPrimeFactorTables(Accelerator accelerator) public static SmallPrimeFactorTables EnsureSmallPrimeFactorTables(KernelContainer kernels, Accelerator accelerator)
{ {
var kernels = GetKernels(accelerator);
if (kernels.SmallPrimeFactorsPrimes is { } primeBuffer && kernels.SmallPrimeFactorsSquares is { } squareBuffer) if (kernels.SmallPrimeFactorsPrimes is { } primeBuffer && kernels.SmallPrimeFactorsSquares is { } squareBuffer)
{ {
return new SmallPrimeFactorTables(primeBuffer, squareBuffer, (int)primeBuffer.Length); return new SmallPrimeFactorTables(primeBuffer, squareBuffer, (int)primeBuffer.Length);
} }
lock (kernels)
{
if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares) if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares)
{ {
return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.Length); return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.Length);
@@ -236,12 +233,8 @@ public class GpuKernelPool
return new SmallPrimeFactorTables(devicePrimes, deviceSquares, hostPrimes.Length); return new SmallPrimeFactorTables(devicePrimes, deviceSquares, hostPrimes.Length);
} }
}
public static SmallPrimeFactorScratch EnsureSmallPrimeFactorScratch(Accelerator accelerator, int slotCount) public static SmallPrimeFactorScratch EnsureSmallPrimeFactorScratch(KernelContainer kernels, Accelerator accelerator, int slotCount)
{
var kernels = GetKernels(accelerator);
lock (kernels)
{ {
MemoryBuffer1D<ulong, Stride1D.Dense>? primeSlots = kernels.SmallPrimeFactorPrimeSlots; MemoryBuffer1D<ulong, Stride1D.Dense>? primeSlots = kernels.SmallPrimeFactorPrimeSlots;
if (primeSlots is null || primeSlots.Length < slotCount) if (primeSlots is null || primeSlots.Length < slotCount)
@@ -275,15 +268,41 @@ public class GpuKernelPool
return new SmallPrimeFactorScratch(primeSlots!, exponentSlots!, countSlot!, remainingSlot!); return new SmallPrimeFactorScratch(primeSlots!, exponentSlots!, countSlot!, remainingSlot!);
} }
public static SpecialMaxScratch EnsureSpecialMaxScratch(KernelContainer kernels, Accelerator accelerator, int factorCapacity)
{
MemoryBuffer1D<ulong, Stride1D.Dense>? factorValues = kernels.SpecialMaxFactors;
if (factorValues is null || factorValues.Length < factorCapacity)
{
factorValues?.Dispose();
factorValues = accelerator.Allocate1D<ulong>(factorCapacity);
kernels.SpecialMaxFactors = factorValues;
}
MemoryBuffer1D<ulong, Stride1D.Dense>? candidateValues = kernels.SpecialMaxCandidates;
if (candidateValues is null || candidateValues.Length < factorCapacity)
{
candidateValues?.Dispose();
candidateValues = accelerator.Allocate1D<ulong>(factorCapacity);
kernels.SpecialMaxCandidates = candidateValues;
}
MemoryBuffer1D<ushort, Stride1D.Dense>? resultSlot = kernels.SpecialMaxResult;
if (resultSlot is null)
{
resultSlot = accelerator.Allocate1D<ushort>(1);
kernels.SpecialMaxResult = resultSlot;
}
return new SpecialMaxScratch(factorValues!, candidateValues!, resultSlot!);
} }
public static GpuKernelLease GetKernel(bool useGpuOrder) public static GpuKernelLease GetKernel(bool useGpuOrder)
{ {
var limiter = GpuPrimeWorkLimiter.Acquire(); GpuPrimeWorkLimiter.Acquire();
var gpu = RentPreferred(preferCpu: !useGpuOrder); var gpu = Rent();
var accelerator = gpu.Accelerator; var kernels = GetKernels();
var kernels = GetKernels(accelerator); return GpuKernelLease.Rent(gpu, kernels);
return GpuKernelLease.Rent(limiter, gpu, kernels);
} }
/// <summary> /// <summary>
@@ -293,12 +312,10 @@ public class GpuKernelPool
public static void Run(Action<Accelerator, AcceleratorStream> action) public static void Run(Action<Accelerator, AcceleratorStream> action)
{ {
var lease = GetKernel(useGpuOrder: true); var lease = GetKernel(useGpuOrder: true);
var execution = lease.EnterExecutionScope();
var accelerator = lease.Accelerator; var accelerator = lease.Accelerator;
var stream = accelerator.CreateStream(); var stream = accelerator.CreateStream();
action(accelerator, stream); action(accelerator, stream);
stream.Dispose(); stream.Dispose();
execution.Dispose();
lease.Dispose(); lease.Dispose();
} }
+8 -36
View File
@@ -1,29 +1,23 @@
using System;
using System.Threading;
namespace PerfectNumbers.Core.Gpu; 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 IDisposable Acquire() public static void Acquire() => _semaphore.Wait();
{ 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 < 1) if (value <= 0)
{ {
value = 1; value = 1;
} }
lock (_lock)
{
if (value == _currentLimit) if (value == _currentLimit)
{ {
return; return;
@@ -31,31 +25,9 @@ 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;
}
} }
} }
@@ -0,0 +1,39 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core;
namespace PerfectNumbers.Core.Gpu;
internal static class GpuStaticTableInitializer
{
private static readonly ConcurrentDictionary<Accelerator, byte> Initialized = new(AcceleratorReferenceComparer.Instance);
internal static void EnsureStaticTables(KernelContainer kernels, Accelerator accelerator)
{
Initialized.GetOrAdd(accelerator, acc =>
{
PrimeTester.PrimeTesterGpuContextPool.EnsureStaticTables(kernels, acc);
GpuKernelPool.PreloadStaticTables(kernels, acc);
PrimeOrderGpuHeuristics.PreloadStaticTables( acc);
return 0;
});
}
private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
{
internal static AcceleratorReferenceComparer Instance { get; } = new();
public bool Equals(Accelerator? x, Accelerator? y)
{
return ReferenceEquals(x, y);
}
public int GetHashCode(Accelerator obj)
{
return RuntimeHelpers.GetHashCode(obj);
}
}
}
+6 -13
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 IDisposable Acquire() public static Lease Acquire()
{ {
// TODO: Replace the per-call Releaser allocation with the pooled struct-based guard from the // TODO: Replace the per-call Lease 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 Releaser(sem); return new Lease(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 Releaser instances. // that may still be in use by existing Lease 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,25 +40,18 @@ public static class GpuWorkLimiter
} }
} }
private sealed class Releaser : IDisposable public sealed class Lease
{ {
private readonly SemaphoreSlim _sem; private readonly SemaphoreSlim _sem;
private bool _disposed;
public Releaser(SemaphoreSlim sem) internal Lease(SemaphoreSlim sem)
{ {
_sem = sem; _sem = sem;
} }
public void Dispose() public void Dispose()
{ {
if (_disposed)
{
return;
}
_sem.Release(); _sem.Release();
_disposed = true;
} }
} }
} }
@@ -0,0 +1,146 @@
using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
internal readonly struct HeuristicGpuDivisorTables
{
public static readonly HeuristicGpuDivisorTables Shared = default;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding3;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding9;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding3;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding9;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupADivisors;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupADivisorSquares;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorSquaresEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorSquaresEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding9;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorSquaresEnding9;
public HeuristicGpuDivisorTables(
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,
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)
{
CombinedDivisorsEnding1 = combinedDivisorsEnding1;
CombinedDivisorsEnding3 = combinedDivisorsEnding3;
CombinedDivisorsEnding7 = combinedDivisorsEnding7;
CombinedDivisorsEnding9 = combinedDivisorsEnding9;
CombinedDivisorSquaresEnding1 = combinedDivisorSquaresEnding1;
CombinedDivisorSquaresEnding3 = combinedDivisorSquaresEnding3;
CombinedDivisorSquaresEnding7 = combinedDivisorSquaresEnding7;
CombinedDivisorSquaresEnding9 = combinedDivisorSquaresEnding9;
GroupADivisors = groupADivisors;
GroupADivisorSquares = groupADivisorSquares;
GroupBDivisorsEnding1 = groupBDivisorsEnding1;
GroupBDivisorSquaresEnding1 = groupBDivisorSquaresEnding1;
GroupBDivisorsEnding7 = groupBDivisorsEnding7;
GroupBDivisorSquaresEnding7 = groupBDivisorSquaresEnding7;
GroupBDivisorsEnding9 = groupBDivisorsEnding9;
GroupBDivisorSquaresEnding9 = groupBDivisorSquaresEnding9;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectCombined(byte ending)
{
return ending switch
{
1 => CombinedDivisorsEnding1,
3 => CombinedDivisorsEnding3,
7 => CombinedDivisorsEnding7,
9 => CombinedDivisorsEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectCombinedSquares(byte ending)
{
return ending switch
{
1 => CombinedDivisorSquaresEnding1,
3 => CombinedDivisorSquaresEnding3,
7 => CombinedDivisorSquaresEnding7,
9 => CombinedDivisorSquaresEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectGroupB(byte ending)
{
return ending switch
{
1 => GroupBDivisorsEnding1,
7 => GroupBDivisorsEnding7,
9 => GroupBDivisorsEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectGroupBSquares(byte ending)
{
return ending switch
{
1 => GroupBDivisorSquaresEnding1,
7 => GroupBDivisorSquaresEnding7,
9 => GroupBDivisorSquaresEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectDivisors(HeuristicGpuDivisorTableKind kind, byte ending)
{
return kind switch
{
HeuristicGpuDivisorTableKind.GroupA => GroupADivisors,
HeuristicGpuDivisorTableKind.GroupBEnding1 => GroupBDivisorsEnding1,
HeuristicGpuDivisorTableKind.GroupBEnding7 => GroupBDivisorsEnding7,
HeuristicGpuDivisorTableKind.GroupBEnding9 => GroupBDivisorsEnding9,
HeuristicGpuDivisorTableKind.Combined => SelectCombined(ending),
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectDivisorSquares(HeuristicGpuDivisorTableKind kind, byte ending)
{
return kind switch
{
HeuristicGpuDivisorTableKind.GroupA => GroupADivisorSquares,
HeuristicGpuDivisorTableKind.GroupBEnding1 => GroupBDivisorSquaresEnding1,
HeuristicGpuDivisorTableKind.GroupBEnding7 => GroupBDivisorSquaresEnding7,
HeuristicGpuDivisorTableKind.GroupBEnding9 => GroupBDivisorSquaresEnding9,
HeuristicGpuDivisorTableKind.Combined => SelectCombinedSquares(ending),
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
internal static void InitializeShared(in HeuristicGpuDivisorTables tables)
{
Unsafe.AsRef(in Shared) = tables;
}
}
@@ -36,30 +36,45 @@ internal static class DivisorByDivisorKernels
byte firstHitValue; byte firstHitValue;
ulong previousExponent = firstExponent; ulong previousExponent = firstExponent;
ulong cycleRemainder = 0UL; ulong cycleRemainder = 0UL;
if (cycleLength == 0UL) bool hasCycle = cycleLength != 0UL;
{ if (hasCycle)
firstHitValue = firstUnity ? (byte)1 : (byte)0;
}
else
{ {
cycleRemainder = firstExponent % cycleLength; cycleRemainder = firstExponent % cycleLength;
firstHitValue = cycleRemainder == 0UL && firstUnity ? (byte)1 : (byte)0; firstHitValue = cycleRemainder == 0UL && firstUnity ? (byte)1 : (byte)0;
} }
else
{
firstHitValue = firstUnity ? (byte)1 : (byte)0;
}
hits[offset] = firstHitValue; hits[offset] = firstHitValue;
byte anyHit = firstHitValue; byte anyHit = firstHitValue;
for (int i = 1; i < count; i++) int endIndex = offset + count;
if (hasCycle)
{ {
ulong exponent = exponents[offset + i]; for (int exponentIndex = offset + 1; exponentIndex < endIndex; exponentIndex++)
{
ulong exponent = exponents[exponentIndex];
ulong delta = exponent - previousExponent; ulong delta = exponent - previousExponent;
previousExponent = exponent; previousExponent = exponent;
if (cycleLength != 0UL) if (delta >= cycleLength)
{ {
var remainderValue = new GpuUInt128(cycleRemainder); delta -= cycleLength;
remainderValue.AddMod(delta, cycleLength); if (delta >= cycleLength)
cycleRemainder = remainderValue.Low; {
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)
@@ -67,13 +82,17 @@ internal static class DivisorByDivisorKernels
hit = stepper.ComputeNextIsUnityGpu(exponent) ? (byte)1 : (byte)0; hit = stepper.ComputeNextIsUnityGpu(exponent) ? (byte)1 : (byte)0;
} }
hits[offset + i] = hit; hits[exponentIndex] = hit;
anyHit |= hit; anyHit |= hit;
} }
}
else else
{ {
for (int exponentIndex = offset + 1; exponentIndex < endIndex; exponentIndex++)
{
ulong exponent = exponents[exponentIndex];
byte hit = stepper.ComputeNextIsUnityGpu(exponent) ? (byte)1 : (byte)0; byte hit = stepper.ComputeNextIsUnityGpu(exponent) ? (byte)1 : (byte)0;
hits[offset + i] = hit; hits[exponentIndex] = hit;
anyHit |= hit; anyHit |= hit;
} }
} }
@@ -0,0 +1,80 @@
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core;
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<ushort, 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]))
{
resultOut[0] = 0;
return;
}
for (int i = 1; i < actual; i++)
{
if (stepper.ComputeNextIsUnityGpu(candidates[i]))
{
resultOut[0] = 0;
return;
}
}
resultOut[0] = 1;
}
}
@@ -1,33 +1,73 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Algorithms; using ILGPU.Algorithms;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
internal enum HeuristicGpuDivisorTableKind : byte
{
GroupA = 0,
GroupBEnding1 = 1,
GroupBEnding7 = 7,
GroupBEnding9 = 9,
Combined = 255,
}
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(Index1D index, ArrayView<ulong> numbers, ArrayView<uint> smallPrimes, ArrayView<byte> results) public static void SmallPrimeSieveKernel(
Index1D index,
ArrayView<ulong> numbers,
ArrayView<uint> smallPrimes,
ArrayView<uint> smallPrimesLastOne,
ArrayView<uint> smallPrimesLastSeven,
ArrayView<uint> smallPrimesLastThree,
ArrayView<uint> smallPrimesLastNine,
ArrayView<ulong> smallPrimesPow2,
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;
// EvenPerfectBitScanner filters candidates so production GPU launches only see: ArrayView<uint> primes = smallPrimes;
// - n >= 31 (the scanner starts at 136,279,841 and test mode seeds 31) ArrayView<ulong> primeSquares = smallPrimesPow2;
// - n odd (candidate generators and ModResidueTracker eliminate even exponents) ulong lastDigit = n % 10UL;
// - n not divisible by 5 (residue prefiltering and AddPrimes transform cover these composites) switch (lastDigit)
// 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;
}
long length = smallPrimes.Length; int length = (int)primes.Length;
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
ulong prime = smallPrimes[i]; ulong primeSquare = primeSquares[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;
@@ -38,16 +78,37 @@ internal static class PrimeTesterKernels
results[index] = result; results[index] = result;
} }
public static void HeuristicTrialDivisionKernel(Index1D index, ArrayView<ulong> divisors, ulong n, ArrayView<byte> results) public static void HeuristicTrialDivisionKernel(
Index1D index,
ArrayView<int> resultFlag,
ulong n,
ulong maxDivisorSquare,
HeuristicGpuDivisorTableKind tableKind,
HeuristicGpuDivisorTables tables)
{ {
ulong divisor = divisors[index]; byte nMod10 = (byte)(n % 10UL);
if (divisor <= 1UL) ArrayView1D<ulong, Stride1D.Dense> divisors = tables.SelectDivisors(tableKind, nMod10);
ArrayView1D<ulong, Stride1D.Dense> divisorSquares = tables.SelectDivisorSquares(tableKind, nMod10);
int divisorLength = (int)divisorSquares.Length;
int threadIndex = index;
if (threadIndex >= divisorLength)
{ {
results[index] = 0;
return; return;
} }
results[index] = n % divisor == 0UL ? (byte)1 : (byte)0; 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)
@@ -94,7 +94,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
var gpuLease = RentAccelerator(); var gpuLease = RentAccelerator();
var accelerator = gpuLease.Accelerator; var accelerator = gpuLease.Accelerator;
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);
@@ -112,7 +112,6 @@ 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,
@@ -122,7 +121,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
out processedCount); out processedCount);
ReturnBatchResources(resources); ReturnBatchResources(resources);
Monitor.Exit(gpuLease.ExecutionLock); // Monitor.Exit(gpuLease.ExecutionLock);
ReturnAccelerator(gpuLease); ReturnAccelerator(gpuLease);
if (composite) if (composite)
@@ -177,7 +176,6 @@ 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,
@@ -192,7 +190,15 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
processedCount = 0UL; processedCount = 0UL;
lastProcessed = 0UL; lastProcessed = 0UL;
int chunkCapacity = Math.Max(1, batchCapacity); // EvenPerfectBitScanner always configures non-empty GPU divisor buffers, so the defensive guard stays
// commented out. Tests and benchmarks that rely on zero-capacity buffers must be updated instead of re-enabling
// this branch.
// if (batchCapacity <= 0)
// {
// throw new InvalidOperationException("Divisor buffers must be non-empty.");
// }
int chunkCountBaseline = batchCapacity;
UInt128 twoP128 = (UInt128)prime << 1; UInt128 twoP128 = (UInt128)prime << 1;
UInt128 allowedMax128 = allowedMax; UInt128 allowedMax128 = allowedMax;
@@ -209,77 +215,103 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128; ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128;
ulong currentK = 1UL; ulong remainingCount = maxK;
byte step10 = (byte)(twoP128 % 10UL); ulong stepValue = (ulong)twoP128;
byte step8 = (byte)(twoP128 % 8UL); byte step10 = (byte)(stepValue % 10UL);
byte step5 = (byte)(twoP128 % 5UL); byte step8 = (byte)(stepValue & 7UL);
byte step3 = (byte)(twoP128 % 3UL); byte step3 = (byte)(stepValue % 3UL);
byte step7 = (byte)(twoP128 % 7UL); byte step7 = (byte)(stepValue % 7UL);
byte step11 = (byte)(twoP128 % 11UL); byte step11 = (byte)(stepValue % 11UL);
UInt128 currentDivisor128 = firstDivisor128; UInt128 currentDivisor128 = firstDivisor128;
byte remainder10 = (byte)((ulong)(currentDivisor128 % 10UL)); ulong currentDivisorValue = (ulong)currentDivisor128;
byte remainder8 = (byte)((ulong)(currentDivisor128 & 7UL)); byte remainder10 = (byte)(currentDivisorValue % 10UL);
byte remainder5 = (byte)((ulong)(currentDivisor128 % 5UL)); byte remainder8 = (byte)(currentDivisorValue & 7UL);
byte remainder3 = (byte)((ulong)(currentDivisor128 % 3UL)); byte remainder3 = (byte)(currentDivisorValue % 3UL);
byte remainder7 = (byte)((ulong)(currentDivisor128 % 7UL)); byte remainder7 = (byte)(currentDivisorValue % 7UL);
byte remainder11 = (byte)((ulong)(currentDivisor128 % 11UL)); byte remainder11 = (byte)(currentDivisorValue % 11UL);
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
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();
Span<int> offsetStorage = offsets.AsSpan(); Span<int> offsetStorage = offsets.AsSpan();
Span<int> countStorage = counts.AsSpan(); Span<int> countStorage = counts.AsSpan();
Span<ulong> cycleStorage = cycles.AsSpan(); Span<ulong> cycleStorage = cycles.AsSpan();
while (currentK <= maxK && !composite) Span<ulong> divisorSpan = divisorStorage;
Span<ulong> exponentSpan = exponentStorage;
Span<GpuDivisorPartialData> divisorDataSpan = divisorDataStorage;
Span<int> offsetSpan = offsetStorage;
Span<int> countSpan = countStorage;
Span<ulong> cycleSpan = cycleStorage;
ref GpuDivisorPartialData divisorDataRef = ref MemoryMarshal.GetReference(divisorDataSpan);
ref int offsetRef = ref MemoryMarshal.GetReference(offsetSpan);
ref int countRef = ref MemoryMarshal.GetReference(countSpan);
ref ulong cycleRef = ref MemoryMarshal.GetReference(cycleSpan);
ref ulong exponentRef = ref MemoryMarshal.GetReference(exponentSpan);
ArrayView1D<GpuDivisorPartialData, Stride1D.Dense> divisorDataView = divisorDataBuffer.View;
ArrayView1D<int, Stride1D.Dense> offsetView = offsetBuffer.View;
ArrayView1D<int, Stride1D.Dense> countView = countBuffer.View;
ArrayView1D<ulong, Stride1D.Dense> cycleView = cycleBuffer.View;
ArrayView1D<ulong, Stride1D.Dense> exponentViewDevice = exponentBuffer.View;
ArrayView1D<byte, Stride1D.Dense> hitsView = hitsBuffer.View;
ArrayView1D<int, Stride1D.Dense> hitIndexView = hitIndexBuffer.View;
while (remainingCount > 0UL && !composite)
{ {
int chunkCount = Math.Min(chunkCapacity, batchCapacity); int chunkCount = chunkCountBaseline;
ulong remainingK = maxK - currentK + 1UL; if ((ulong)chunkCount > remainingCount)
if ((ulong)chunkCount > remainingK)
{ {
chunkCount = (int)remainingK; chunkCount = (int)remainingCount;
} }
if (chunkCount <= 0)
{
processedAll = true;
break;
}
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++)
{ {
ulong candidate = (ulong)nextDivisor128; bool passesSmallModuli = localRemainder3 != 0 && localRemainder7 != 0 && localRemainder11 != 0;
if (passesSmallModuli && (localRemainder8 == 1 || localRemainder8 == 7) && ((decimalMask >> localRemainder10) & 1) != 0)
if (DivisorGenerator.IsValidDivisor(localRemainder10, localRemainder8, localRemainder3, localRemainder5, localRemainder7, localRemainder11, lastDigit))
{ {
filteredStorage[filteredCount++] = candidate; ulong divisorValue = (ulong)nextDivisor128;
MontgomeryDivisorData montgomeryData = MontgomeryDivisorData.FromModulus(divisorValue);
ulong divisorCycle = ResolveDivisorCycle(divisorValue, prime, in montgomeryData);
if (divisorCycle == prime)
{
processedCount += (ulong)(i + 1);
lastProcessed = divisorValue;
coveredRange = true;
return true;
}
divisorSpan[admissibleCount] = divisorValue;
divisorDataSpan[admissibleCount] = new GpuDivisorPartialData(divisorValue);
offsetSpan[admissibleCount] = admissibleCount;
countSpan[admissibleCount] = 1;
cycleSpan[admissibleCount] = divisorCycle;
exponentSpan[admissibleCount] = prime;
admissibleCount++;
} }
nextDivisor128 += twoP128; nextDivisor128 += twoP128;
localRemainder10 = AddMod(localRemainder10, step10, 10); localRemainder10 = AddMod10(localRemainder10, step10);
localRemainder8 = AddMod(localRemainder8, step8, 8); localRemainder8 = AddMod8(localRemainder8, step8);
localRemainder5 = AddMod(localRemainder5, step5, 5); localRemainder3 = AddMod3(localRemainder3, step3);
localRemainder3 = AddMod(localRemainder3, step3, 3); localRemainder7 = AddMod7(localRemainder7, step7);
localRemainder7 = AddMod(localRemainder7, step7, 7); localRemainder11 = AddMod11(localRemainder11, step11);
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;
@@ -289,69 +321,23 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
UInt128 lastDivisor128 = nextDivisor128 - twoP128; UInt128 lastDivisor128 = nextDivisor128 - twoP128;
lastProcessed = (ulong)lastDivisor128; lastProcessed = (ulong)lastDivisor128;
currentDivisor128 = nextDivisor128; currentDivisor128 = nextDivisor128;
remainingCount -= (ulong)chunkCount;
currentK += (ulong)chunkCount;
processedAll = currentK > maxK;
if (filteredCount == 0)
{
continue;
}
Span<ulong> filteredDivisorsSpan = filteredStorage[..filteredCount];
Span<ulong> divisorSpan = divisorStorage;
Span<ulong> exponentSpan = exponentStorage;
Span<GpuDivisorPartialData> divisorDataSpan = divisorDataStorage;
Span<int> offsetSpan = offsetStorage;
Span<int> countSpan = countStorage;
Span<ulong> cycleSpan = cycleStorage;
int admissibleCount = 0;
int exponentIndex = 0;
for (int i = 0; i < filteredCount; i++)
{
ulong divisorValue = filteredDivisorsSpan[i];
MontgomeryDivisorData montgomeryData = MontgomeryDivisorData.FromModulus(divisorValue);
ulong divisorCycle = ResolveDivisorCycle(divisorValue, prime, in montgomeryData);
if (divisorCycle != prime)
{
continue;
}
divisorSpan[admissibleCount] = divisorValue;
exponentSpan[exponentIndex] = prime;
divisorDataSpan[admissibleCount] = new GpuDivisorPartialData(divisorValue);
offsetSpan[admissibleCount] = exponentIndex;
countSpan[admissibleCount] = 1;
cycleSpan[admissibleCount] = divisorCycle;
admissibleCount++;
exponentIndex++;
}
if (admissibleCount == 0) if (admissibleCount == 0)
{ {
continue; continue;
} }
ArrayView1D<GpuDivisorPartialData, Stride1D.Dense> divisorView = divisorDataBuffer.View.SubView(0, admissibleCount); divisorDataView.CopyFromCPU(ref divisorDataRef, admissibleCount);
ArrayView1D<int, Stride1D.Dense> offsetView = offsetBuffer.View.SubView(0, admissibleCount); offsetView.CopyFromCPU(ref offsetRef, admissibleCount);
ArrayView1D<int, Stride1D.Dense> countView = countBuffer.View.SubView(0, admissibleCount); countView.CopyFromCPU(ref countRef, admissibleCount);
ArrayView1D<ulong, Stride1D.Dense> cycleView = cycleBuffer.View.SubView(0, admissibleCount); cycleView.CopyFromCPU(ref cycleRef, admissibleCount);
ArrayView1D<ulong, Stride1D.Dense> exponentView = exponentBuffer.View.SubView(0, exponentIndex); exponentViewDevice.CopyFromCPU(ref exponentRef, admissibleCount);
ArrayView1D<byte, Stride1D.Dense> hitsView = hitsBuffer.View.SubView(0, exponentIndex);
ArrayView1D<int, Stride1D.Dense> hitIndexView = hitIndexBuffer.View.SubView(0, 1);
divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), admissibleCount);
offsetView.CopyFromCPU(ref MemoryMarshal.GetReference(offsetSpan), admissibleCount);
countView.CopyFromCPU(ref MemoryMarshal.GetReference(countSpan), admissibleCount);
cycleView.CopyFromCPU(ref MemoryMarshal.GetReference(cycleSpan), admissibleCount);
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSpan), exponentIndex);
int sentinel = int.MaxValue; int sentinel = int.MaxValue;
hitIndexView.CopyFromCPU(ref sentinel, 1); hitIndexView.CopyFromCPU(ref sentinel, 1);
kernel(admissibleCount, divisorView, offsetView, countView, exponentView, cycleView, hitsView, hitIndexView); kernel(admissibleCount, divisorDataView, offsetView, countView, exponentViewDevice, cycleView, hitsView, hitIndexView);
int firstHit = sentinel; int firstHit = sentinel;
hitIndexView.CopyToCPU(ref firstHit, 1); hitIndexView.CopyToCPU(ref firstHit, 1);
@@ -362,6 +348,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
lastProcessed = hitFound ? divisorSpan[hitIndex] : divisorSpan[lastIndex]; lastProcessed = hitFound ? divisorSpan[hitIndex] : divisorSpan[lastIndex];
} }
processedAll = remainingCount == 0UL;
coveredRange = composite || processedAll || (currentDivisor128 > allowedMax128); coveredRange = composite || processedAll || (currentDivisor128 > allowedMax128);
return composite; return composite;
} }
@@ -379,23 +366,67 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod(byte value, byte delta, byte modulus) private static byte AddMod3(byte value, byte delta)
{ {
int result = value + delta; const int Modulus = 3;
if (result >= modulus) int sum = value + delta;
if (sum >= Modulus)
{ {
result -= modulus; sum -= Modulus;
if (result >= modulus)
{
result -= modulus;
}
} }
return (byte)result; return (byte)sum;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod7(byte value, byte delta)
{
const int Modulus = 7;
int sum = value + delta;
if (sum >= Modulus)
{
sum -= Modulus;
}
return (byte)sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod10(byte value, byte delta)
{
const int Modulus = 10;
int sum = value + delta;
if (sum >= Modulus)
{
sum -= Modulus;
}
return (byte)sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod11(byte value, byte delta)
{
const int Modulus = 11;
int sum = value + delta;
if (sum >= Modulus)
{
sum -= Modulus;
}
return (byte)sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod8(byte value, byte delta)
{
return (byte)((value + delta) & 7);
}
private static ulong ComputeDivisorLimitFromMaxPrimeGpu(ulong maxPrime) private static ulong ComputeDivisorLimitFromMaxPrimeGpu(ulong maxPrime)
@@ -421,13 +452,25 @@ 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 divisorLimit; return cappedLimit;
} }
ulong computedLimit = (1UL << (int)(prime - 1UL)) - 1UL; ulong computedLimit = (1UL << (int)(prime - 1UL)) - 1UL;
return computedLimit < divisorLimit ? computedLimit : divisorLimit; if (prime < 10_000_000UL && computedLimit > cappedLimit)
{
computedLimit = cappedLimit;
}
return computedLimit < cappedLimit ? computedLimit : cappedLimit;
} }
public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
@@ -445,19 +488,17 @@ 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;
_lease = GpuContextPool.RentPreferred(preferCpu: false); _lease = GpuContextPool.Rent();
_accelerator = _lease.Accelerator; _accelerator = _lease.Accelerator;
_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)
@@ -504,11 +545,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
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)
@@ -544,7 +580,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
} }
Monitor.Enter(_lease.ExecutionLock); // Monitor.Enter(_lease.ExecutionLock);
EnsureExecutionResourcesLocked(length); EnsureExecutionResourcesLocked(length);
@@ -557,51 +593,39 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ArrayView1D<byte, Stride1D.Dense> hitView = _hitBuffer.View; ArrayView1D<byte, Stride1D.Dense> hitView = _hitBuffer.View;
GpuDivisorPartialData partialData = new GpuDivisorPartialData(divisor); GpuDivisorPartialData partialData = new GpuDivisorPartialData(divisor);
divisorView.SubView(0, 1).CopyFromCPU(ref partialData, 1); divisorView.CopyFromCPU(ref partialData, 1);
int offsetValue = 0; int offsetValue = 0;
offsetView.SubView(0, 1).CopyFromCPU(ref offsetValue, 1); offsetView.CopyFromCPU(ref offsetValue, 1);
int countValue = length; int countValue = length;
countView.SubView(0, 1).CopyFromCPU(ref countValue, 1); countView.CopyFromCPU(ref countValue, 1);
ulong cycleValue = cycle; ulong cycleValue = cycle;
cycleView.SubView(0, 1).CopyFromCPU(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);
ArrayView1D<ulong, Stride1D.Dense> exponentSlice = exponentView.SubView(0, length); exponentView.CopyFromCPU(ref hostRef, length);
exponentSlice.CopyFromCPU(ref MemoryMarshal.GetReference(hostSpan), length);
ArrayView1D<byte, Stride1D.Dense> hitSliceView = hitView.SubView(0, length);
int sentinel = int.MaxValue; int sentinel = int.MaxValue;
firstHitView.SubView(0, 1).CopyFromCPU(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(1, divisorView, offsetView, countView, exponentSlice, cycleView, hitSliceView, firstHitView); kernel(1, divisorView, offsetView, countView, exponentView, cycleView, hitView, firstHitView);
Span<byte> hitSlice = hits.Slice(0, length); Span<byte> hitSlice = hits.Slice(0, length);
hitSliceView.CopyToCPU(ref MemoryMarshal.GetReference(hitSlice), length); ref byte hitRef = ref MemoryMarshal.GetReference(hitSlice);
hitView.CopyToCPU(ref hitRef, length);
Monitor.Exit(_lease.ExecutionLock); // Monitor.Exit(_lease.ExecutionLock);
} }
public void Dispose() public void Dispose()
{ {
if (_disposed)
{
return;
}
_disposed = true;
_owner._sessionPool.Add(this); _owner._sessionPool.Add(this);
} }
} }
@@ -633,7 +657,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
return lease; return lease;
} }
return GpuContextPool.RentPreferred(preferCpu: false); return GpuContextPool.Rent();
} }
private void ReturnAccelerator(GpuContextPool.GpuContextLease lease) => _acceleratorPool.Add(lease); private void ReturnAccelerator(GpuContextPool.GpuContextLease lease) => _acceleratorPool.Add(lease);
@@ -651,7 +675,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 : IDisposable private sealed class BatchResources
{ {
private Accelerator? _accelerator; private Accelerator? _accelerator;
private MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense>? _divisorDataBuffer; private MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense>? _divisorDataBuffer;
@@ -688,8 +712,6 @@ 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!;
@@ -764,7 +786,6 @@ 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);
@@ -803,14 +824,12 @@ 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>();
@@ -37,7 +37,7 @@ public sealed class MersenneNumberDivisorGpuTester
public bool IsDivisible(ulong exponent, in ReadOnlyGpuUInt128 divisor) public bool IsDivisible(ulong exponent, in ReadOnlyGpuUInt128 divisor)
{ {
var gpu = GpuContextPool.RentPreferred(preferCpu: false); var gpu = GpuContextPool.Rent();
var accelerator = gpu.Accelerator; var accelerator = gpu.Accelerator;
var kernel = GetKernel(accelerator); var kernel = GetKernel(accelerator);
var resultBuffer = _resultBuffers.GetOrAdd(accelerator, acc => acc.Allocate1D<byte>(1)); var resultBuffer = _resultBuffers.GetOrAdd(accelerator, acc => acc.Allocate1D<byte>(1));
@@ -17,7 +17,6 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
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}).");
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
var execution = gpuLease.EnterExecutionScope();
var accelerator = gpuLease.Accelerator; var accelerator = gpuLease.Accelerator;
var stream = gpuLease.Stream; var stream = gpuLease.Stream;
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy
@@ -32,11 +31,11 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
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.EnsureSmallCyclesOnDevice(accelerator); var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(gpuLease.Kernels, accelerator);
ResiduePrimeViews primeViews = default; ResiduePrimeViews primeViews = default;
if (_kernelType == GpuKernelType.Pow2Mod) if (_kernelType == GpuKernelType.Pow2Mod)
{ {
primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(accelerator); primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(gpuLease.Kernels, accelerator);
} }
var orderBuffer = accelerator.Allocate1D<ulong>(batchSize); var orderBuffer = accelerator.Allocate1D<ulong>(batchSize);
@@ -47,8 +46,6 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
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;
@@ -122,13 +119,9 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
kStart += (UInt128)currentSize; kStart += (UInt128)currentSize;
} }
}
finally
{
ArrayPool<ulong>.Shared.Return(orderArray); ArrayPool<ulong>.Shared.Return(orderArray);
orderBuffer.Dispose(); orderBuffer.Dispose();
execution.Dispose();
gpuLease.Dispose(); gpuLease.Dispose();
} }
}
} }
@@ -38,16 +38,6 @@ public class MersenneNumberLucasLehmerGpuTester
// 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:
@@ -64,13 +54,13 @@ public class MersenneNumberLucasLehmerGpuTester
return false; return false;
} }
var limiter = GpuPrimeWorkLimiter.Acquire(); 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(); GpuPrimeWorkLimiter.Release();
throw new NotSupportedException("NTT parameters for the given exponent are not available."); throw new NotSupportedException("NTT parameters for the given exponent are not available.");
} }
@@ -78,7 +68,7 @@ public class MersenneNumberLucasLehmerGpuTester
} }
else else
{ {
var gpu = GpuContextPool.RentPreferred(preferCpu: !runOnGpu); var gpu = GpuContextPool.Rent();
var accelerator = gpu.Accelerator; var accelerator = gpu.Accelerator;
var kernel = GetKernel(accelerator); var kernel = GetKernel(accelerator);
var modulus = new GpuUInt128(((UInt128)1 << (int)exponent) - 1UL); // TODO: Cache these Mersenne moduli per exponent so LL GPU runs skip rebuilding them every launch. var modulus = new GpuUInt128(((UInt128)1 << (int)exponent) - 1UL); // TODO: Cache these Mersenne moduli per exponent so LL GPU runs skip rebuilding them every launch.
@@ -90,7 +80,7 @@ public class MersenneNumberLucasLehmerGpuTester
gpu.Dispose(); gpu.Dispose();
} }
limiter.Dispose(); GpuPrimeWorkLimiter.Release();
return result; return result;
} }
@@ -110,7 +100,7 @@ public class MersenneNumberLucasLehmerGpuTester
} }
} }
var gpu = GpuContextPool.RentPreferred(preferCpu: false); var gpu = GpuContextPool.Rent();
var accelerator = gpu.Accelerator; var accelerator = gpu.Accelerator;
var kernel = GetBatchKernel(accelerator); var kernel = GetBatchKernel(accelerator);
@@ -152,23 +142,17 @@ public class MersenneNumberLucasLehmerGpuTester
} }
GpuUInt128[] buffer = ArrayPool<GpuUInt128>.Shared.Rent(count); GpuUInt128[] buffer = ArrayPool<GpuUInt128>.Shared.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;
} }
}
finally
{
ArrayPool<GpuUInt128>.Shared.Return(buffer); 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 gpu = GpuContextPool.RentPreferred(preferCpu: !runOnGpu); var gpu = GpuContextPool.Rent();
var accelerator = gpu.Accelerator; var accelerator = gpu.Accelerator;
var addKernel = GetAddSmallKernel(accelerator); var addKernel = GetAddSmallKernel(accelerator);
var subKernel = GetSubSmallKernel(accelerator); var subKernel = GetSubSmallKernel(accelerator);
@@ -10,7 +10,6 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO
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)
{ {
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
var execution = gpuLease.EnterExecutionScope();
var accelerator = gpuLease.Accelerator; var accelerator = gpuLease.Accelerator;
var stream = gpuLease.Stream; var stream = gpuLease.Stream;
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR
@@ -26,11 +25,12 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO
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. var foundBuffer = accelerator.Allocate1D<int>(1); // TODO: Replace this allocation with the pooled device buffer from GpuOrderKernelBenchmarks so repeated scans reuse the pinned staging memory instead of allocating per run.
exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3); 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.EnsureSmallCyclesOnDevice(gpuLease.Kernels, accelerator);
while (kStart <= maxK && Volatile.Read(ref isPrime)) while (kStart <= maxK && Volatile.Read(ref isPrime))
{ {
remaining = maxK - kStart + 1UL; remaining = maxK - kStart + 1UL;
@@ -45,9 +45,7 @@ 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 // 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, kernel(stream, currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, divMul,
kernelArgs, foundBuffer.View, smallCyclesView); kernelArgs, foundBuffer.View, smallCyclesView);
@@ -61,13 +59,8 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO
kStart += (UInt128)currentSize; kStart += (UInt128)currentSize;
} }
}
finally
{
foundBuffer.Dispose(); foundBuffer.Dispose();
execution.Dispose();
gpuLease.Dispose(); gpuLease.Dispose();
} }
}
} }
@@ -26,23 +26,24 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
// GPU residue variant: check 2^p % q == 1 for q = 2*p*k + 1. // GPU residue variant: check 2^p % q == 1 for q = 2*p*k + 1.
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)
{ {
var limiter = GpuPrimeWorkLimiter.Acquire(); GpuPrimeWorkLimiter.Acquire();
var gpuLease = RentAccelerator(); var gpuLease = RentAccelerator();
var accelerator = gpuLease.Accelerator; var accelerator = gpuLease.Accelerator;
Monitor.Enter(gpuLease.ExecutionLock); // Monitor.Enter(gpuLease.ExecutionLock);
var stream = accelerator.CreateStream(); var stream = accelerator.CreateStream();
var kernel = GetPow2ModKernel(accelerator); var kernel = GetPow2ModKernel(accelerator);
var resources = RentResources(accelerator, GpuConstants.ScanBatchSize); var resources = RentResources(accelerator, GpuConstants.ScanBatchSize);
var gpuKernels = GpuKernelPool.GetKernels();
var orderBuffer = resources.OrderBuffer; var orderBuffer = resources.OrderBuffer;
ulong[] orderArray = resources.OrderArray; ulong[] orderArray = resources.OrderArray;
int batchSize = resources.Capacity; int batchSize = resources.Capacity;
// Ensure device has small cycles and primes tables for in-kernel lookup // Ensure device has small cycles and primes tables for in-kernel lookup
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(gpuKernels, accelerator);
ResiduePrimeViews primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(accelerator); ResiduePrimeViews primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(gpuKernels, accelerator);
UInt128 kStart = 1UL; UInt128 kStart = 1UL;
UInt128 limit = maxK + UInt128.One; UInt128 limit = maxK + UInt128.One;
@@ -108,9 +109,10 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
ReturnResources(accelerator, resources); ReturnResources(accelerator, resources);
stream.Dispose(); stream.Dispose();
Monitor.Exit(gpuLease.ExecutionLock); // Monitor.Exit(gpuLease.ExecutionLock);
gpuKernels.Dispose();
ReturnAccelerator(gpuLease); ReturnAccelerator(gpuLease);
limiter.Dispose(); GpuPrimeWorkLimiter.Release();
} }
private 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>> GetPow2ModKernel(Accelerator accelerator) => private 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>> GetPow2ModKernel(Accelerator accelerator) =>
@@ -132,7 +134,7 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
return lease; return lease;
} }
return GpuContextPool.RentPreferred(preferCpu: !_useGpuOrder); return GpuContextPool.Rent();
} }
private void ReturnAccelerator(GpuContextPool.GpuContextLease lease) => _acceleratorPool.Add(lease); private void ReturnAccelerator(GpuContextPool.GpuContextLease lease) => _acceleratorPool.Add(lease);
@@ -161,7 +163,7 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder)
private void ReturnResources(Accelerator accelerator, ResidueResources resources) => private void ReturnResources(Accelerator accelerator, ResidueResources resources) =>
_resourcePools.GetOrAdd(accelerator, static _ => new ConcurrentBag<ResidueResources>()).Add(resources); _resourcePools.GetOrAdd(accelerator, static _ => new ConcurrentBag<ResidueResources>()).Add(resources);
private sealed class ResidueResources : IDisposable private sealed class ResidueResources
{ {
internal ResidueResources(Accelerator accelerator, int capacity) internal ResidueResources(Accelerator accelerator, int capacity)
{ {
+1 -1
View File
@@ -47,7 +47,7 @@ public static class NttGpuMath
private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, int>> BitReverseKernelCache = new(); // TODO: Precompute bit-reversal kernels during boot so we can remove the concurrent lookup overhead flagged in the benchmarks. private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, int>> BitReverseKernelCache = new(); // TODO: Precompute bit-reversal kernels during boot so we can remove the concurrent lookup overhead flagged in the benchmarks.
private sealed class SquareCacheEntry : IDisposable private sealed class SquareCacheEntry
{ {
public MemoryBuffer1D<GpuUInt128, Stride1D.Dense> Buffer { get; } public MemoryBuffer1D<GpuUInt128, Stride1D.Dense> Buffer { get; }
public GpuUInt128 Root { get; } public GpuUInt128 Root { get; }
@@ -18,6 +18,11 @@ internal enum GpuPow2ModStatus
internal static partial class PrimeOrderGpuHeuristics internal static partial class PrimeOrderGpuHeuristics
{ {
internal static void PreloadStaticTables(Accelerator accelerator)
{
_ = GetSmallPrimeDeviceCache(accelerator);
}
private static readonly ConcurrentDictionary<ulong, byte> OverflowedPrimes = new(); private static readonly ConcurrentDictionary<ulong, byte> OverflowedPrimes = new();
private static readonly ConcurrentDictionary<UInt128, byte> OverflowedPrimesWide = new(); private static readonly ConcurrentDictionary<UInt128, byte> OverflowedPrimesWide = new();
private static readonly ConcurrentDictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>> Pow2ModKernelCache = new(); private static readonly ConcurrentDictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>> Pow2ModKernelCache = new();
@@ -194,7 +199,6 @@ internal static partial class PrimeOrderGpuHeuristics
try try
{ {
var lease = GpuKernelPool.GetKernel(useGpuOrder: true); var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
var execution = lease.EnterExecutionScope();
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = lease.Accelerator;
AcceleratorStream stream = lease.Stream; AcceleratorStream stream = lease.Stream;
@@ -248,7 +252,6 @@ internal static partial class PrimeOrderGpuHeuristics
countBuffer.Dispose(); countBuffer.Dispose();
remainingBuffer.Dispose(); remainingBuffer.Dispose();
fullyFactoredBuffer.Dispose(); fullyFactoredBuffer.Dispose();
execution.Dispose();
lease.Dispose(); lease.Dispose();
return true; return true;
} }
@@ -362,7 +365,6 @@ internal static partial class PrimeOrderGpuHeuristics
order = 0UL; order = 0UL;
var lease = GpuKernelPool.GetKernel(useGpuOrder: true); var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
var execution = lease.EnterExecutionScope();
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = lease.Accelerator;
AcceleratorStream stream = lease.Stream; AcceleratorStream stream = lease.Stream;
@@ -467,7 +469,6 @@ internal static partial class PrimeOrderGpuHeuristics
stackProductBuffer.Dispose(); stackProductBuffer.Dispose();
resultBuffer.Dispose(); resultBuffer.Dispose();
statusBuffer.Dispose(); statusBuffer.Dispose();
execution.Dispose();
} }
} }
@@ -526,7 +527,6 @@ internal static partial class PrimeOrderGpuHeuristics
private static bool TryComputeOnGpu(ReadOnlySpan<ulong> exponents, ulong prime, in MontgomeryDivisorData divisorData, Span<ulong> results) private static bool TryComputeOnGpu(ReadOnlySpan<ulong> exponents, ulong prime, in MontgomeryDivisorData divisorData, Span<ulong> results)
{ {
var lease = GpuKernelPool.GetKernel(useGpuOrder: true); var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
var execution = lease.EnterExecutionScope();
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = lease.Accelerator;
AcceleratorStream stream = lease.Stream; AcceleratorStream stream = lease.Stream;
var kernel = GetPow2ModKernel(accelerator); var kernel = GetPow2ModKernel(accelerator);
@@ -551,7 +551,6 @@ internal static partial class PrimeOrderGpuHeuristics
Console.WriteLine($"Exception for {prime} and exponents: {string.Join(",", exponents.ToArray())}."); Console.WriteLine($"Exception for {prime} and exponents: {string.Join(",", exponents.ToArray())}.");
exponentBuffer.Dispose(); exponentBuffer.Dispose();
remainderBuffer.Dispose(); remainderBuffer.Dispose();
execution.Dispose();
lease.Dispose(); lease.Dispose();
throw; throw;
} }
@@ -559,7 +558,6 @@ internal static partial class PrimeOrderGpuHeuristics
remainderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(results), exponents.Length); remainderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(results), exponents.Length);
exponentBuffer.Dispose(); exponentBuffer.Dispose();
remainderBuffer.Dispose(); remainderBuffer.Dispose();
execution.Dispose();
lease.Dispose(); lease.Dispose();
return true; return true;
} }
@@ -587,7 +585,6 @@ internal static partial class PrimeOrderGpuHeuristics
try try
{ {
var execution = lease.EnterExecutionScope();
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = lease.Accelerator;
AcceleratorStream stream = lease.Stream; AcceleratorStream stream = lease.Stream;
var kernel = GetPow2ModWideKernel(accelerator); var kernel = GetPow2ModWideKernel(accelerator);
@@ -628,7 +625,6 @@ internal static partial class PrimeOrderGpuHeuristics
exponentBuffer.Dispose(); exponentBuffer.Dispose();
remainderBuffer.Dispose(); remainderBuffer.Dispose();
execution.Dispose();
return true; return true;
} }
catch (Exception) catch (Exception)
@@ -0,0 +1,19 @@
using System;
using System.Threading;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
internal static class SharedGpuContext
{
private static readonly Lazy<Context> s_context = new(() => ILGPU.Context.CreateDefault(), LazyThreadSafetyMode.ExecutionAndPublication);
private static readonly Lazy<Device> s_device = new(() => s_context.Value.GetPreferredDevice(false), LazyThreadSafetyMode.ExecutionAndPublication);
private static readonly Lazy<Accelerator> s_accelerator = new(() => s_device.Value.CreateAccelerator(s_context.Value), LazyThreadSafetyMode.ExecutionAndPublication);
internal static Context Context => s_context.Value;
internal static Device Device => s_device.Value;
internal static Accelerator Accelerator => s_accelerator.Value;
}
@@ -0,0 +1,697 @@
using Open.Numeric.Primes;
using ILGPU.Runtime;
using System.Runtime.CompilerServices;
using PerfectNumbers.Core.Gpu;
namespace PerfectNumbers.Core;
public sealed class HeuristicCombinedPrimeTester
{
static HeuristicCombinedPrimeTester()
{
HeuristicPrimeSieves.EnsureInitialized();
CombinedDivisorsEnding1TwoAOneB = BuildCombinedDivisors(1, CombinedDivisorPattern.TwoAOneB, out CombinedDivisorsEnding1TwoAOneBSquares);
CombinedDivisorsEnding3TwoAOneB = BuildCombinedDivisors(3, CombinedDivisorPattern.TwoAOneB, out CombinedDivisorsEnding3TwoAOneBSquares);
CombinedDivisorsEnding7TwoAOneB = BuildCombinedDivisors(7, CombinedDivisorPattern.TwoAOneB, out CombinedDivisorsEnding7TwoAOneBSquares);
CombinedDivisorsEnding9TwoAOneB = BuildCombinedDivisors(9, CombinedDivisorPattern.TwoAOneB, out CombinedDivisorsEnding9TwoAOneBSquares);
CombinedDivisorsEnding1OneAOneB = BuildCombinedDivisors(1, CombinedDivisorPattern.OneAOneB, out CombinedDivisorsEnding1OneAOneBSquares);
CombinedDivisorsEnding3OneAOneB = BuildCombinedDivisors(3, CombinedDivisorPattern.OneAOneB, out CombinedDivisorsEnding3OneAOneBSquares);
CombinedDivisorsEnding7OneAOneB = BuildCombinedDivisors(7, CombinedDivisorPattern.OneAOneB, out CombinedDivisorsEnding7OneAOneBSquares);
CombinedDivisorsEnding9OneAOneB = BuildCombinedDivisors(9, CombinedDivisorPattern.OneAOneB, out CombinedDivisorsEnding9OneAOneBSquares);
CombinedDivisorsEnding1ThreeAOneB = BuildCombinedDivisors(1, CombinedDivisorPattern.ThreeAOneB, out CombinedDivisorsEnding1ThreeAOneBSquares);
CombinedDivisorsEnding3ThreeAOneB = BuildCombinedDivisors(3, CombinedDivisorPattern.ThreeAOneB, out CombinedDivisorsEnding3ThreeAOneBSquares);
CombinedDivisorsEnding7ThreeAOneB = BuildCombinedDivisors(7, CombinedDivisorPattern.ThreeAOneB, out CombinedDivisorsEnding7ThreeAOneBSquares);
CombinedDivisorsEnding9ThreeAOneB = BuildCombinedDivisors(9, CombinedDivisorPattern.ThreeAOneB, out CombinedDivisorsEnding9ThreeAOneBSquares);
CombinedDivisorsEnding1ThreeATwoB = BuildCombinedDivisors(1, CombinedDivisorPattern.ThreeATwoB, out CombinedDivisorsEnding1ThreeATwoBSquares);
CombinedDivisorsEnding3ThreeATwoB = BuildCombinedDivisors(3, CombinedDivisorPattern.ThreeATwoB, out CombinedDivisorsEnding3ThreeATwoBSquares);
CombinedDivisorsEnding7ThreeATwoB = BuildCombinedDivisors(7, CombinedDivisorPattern.ThreeATwoB, out CombinedDivisorsEnding7ThreeATwoBSquares);
CombinedDivisorsEnding9ThreeATwoB = BuildCombinedDivisors(9, CombinedDivisorPattern.ThreeATwoB, out CombinedDivisorsEnding9ThreeATwoBSquares);
CombinedDivisorsEnding1 = CombinedDivisorsEnding1OneAOneB;
CombinedDivisorsEnding3 = CombinedDivisorsEnding3OneAOneB;
CombinedDivisorsEnding7 = CombinedDivisorsEnding7OneAOneB;
CombinedDivisorsEnding9 = CombinedDivisorsEnding9OneAOneB;
CombinedDivisorsEnding1Squares = CombinedDivisorsEnding1OneAOneBSquares;
CombinedDivisorsEnding3Squares = CombinedDivisorsEnding3OneAOneBSquares;
CombinedDivisorsEnding7Squares = CombinedDivisorsEnding7OneAOneBSquares;
CombinedDivisorsEnding9Squares = CombinedDivisorsEnding9OneAOneBSquares;
}
public static void EnsureInitialized()
{
// Intentionally left blank. Accessing this method forces the static constructor to run.
}
internal static ReadOnlySpan<ulong> CombinedDivisorsEnding1Span => CombinedDivisorsEnding1;
internal static ReadOnlySpan<ulong> CombinedDivisorsEnding3Span => CombinedDivisorsEnding3;
internal static ReadOnlySpan<ulong> CombinedDivisorsEnding7Span => CombinedDivisorsEnding7;
internal static ReadOnlySpan<ulong> CombinedDivisorsEnding9Span => CombinedDivisorsEnding9;
internal static ReadOnlySpan<ulong> CombinedDivisorsEnding1SquaresSpan => CombinedDivisorsEnding1Squares;
internal static ReadOnlySpan<ulong> CombinedDivisorsEnding3SquaresSpan => CombinedDivisorsEnding3Squares;
internal static ReadOnlySpan<ulong> CombinedDivisorsEnding7SquaresSpan => CombinedDivisorsEnding7Squares;
internal static ReadOnlySpan<ulong> CombinedDivisorsEnding9SquaresSpan => CombinedDivisorsEnding9Squares;
private const ulong Wheel210 = 210UL;
private static readonly ulong[] CombinedDivisorsEnding1TwoAOneB;
private static readonly ulong[] CombinedDivisorsEnding3TwoAOneB;
private static readonly ulong[] CombinedDivisorsEnding7TwoAOneB;
private static readonly ulong[] CombinedDivisorsEnding9TwoAOneB;
private static readonly ulong[] CombinedDivisorsEnding1OneAOneB;
private static readonly ulong[] CombinedDivisorsEnding3OneAOneB;
private static readonly ulong[] CombinedDivisorsEnding7OneAOneB;
private static readonly ulong[] CombinedDivisorsEnding9OneAOneB;
private static readonly ulong[] CombinedDivisorsEnding1ThreeAOneB;
private static readonly ulong[] CombinedDivisorsEnding3ThreeAOneB;
private static readonly ulong[] CombinedDivisorsEnding7ThreeAOneB;
private static readonly ulong[] CombinedDivisorsEnding9ThreeAOneB;
private static readonly ulong[] CombinedDivisorsEnding1ThreeATwoB;
private static readonly ulong[] CombinedDivisorsEnding3ThreeATwoB;
private static readonly ulong[] CombinedDivisorsEnding7ThreeATwoB;
private static readonly ulong[] CombinedDivisorsEnding9ThreeATwoB;
private static readonly ulong[] CombinedDivisorsEnding1;
private static readonly ulong[] CombinedDivisorsEnding3;
private static readonly ulong[] CombinedDivisorsEnding7;
private static readonly ulong[] CombinedDivisorsEnding9;
private static readonly ulong[] CombinedDivisorsEnding1TwoAOneBSquares;
private static readonly ulong[] CombinedDivisorsEnding3TwoAOneBSquares;
private static readonly ulong[] CombinedDivisorsEnding7TwoAOneBSquares;
private static readonly ulong[] CombinedDivisorsEnding9TwoAOneBSquares;
private static readonly ulong[] CombinedDivisorsEnding1OneAOneBSquares;
private static readonly ulong[] CombinedDivisorsEnding3OneAOneBSquares;
private static readonly ulong[] CombinedDivisorsEnding7OneAOneBSquares;
private static readonly ulong[] CombinedDivisorsEnding9OneAOneBSquares;
private static readonly ulong[] CombinedDivisorsEnding1ThreeAOneBSquares;
private static readonly ulong[] CombinedDivisorsEnding3ThreeAOneBSquares;
private static readonly ulong[] CombinedDivisorsEnding7ThreeAOneBSquares;
private static readonly ulong[] CombinedDivisorsEnding9ThreeAOneBSquares;
private static readonly ulong[] CombinedDivisorsEnding1ThreeATwoBSquares;
private static readonly ulong[] CombinedDivisorsEnding3ThreeATwoBSquares;
private static readonly ulong[] CombinedDivisorsEnding7ThreeATwoBSquares;
private static readonly ulong[] CombinedDivisorsEnding9ThreeATwoBSquares;
private static readonly ulong[] CombinedDivisorsEnding1Squares;
private static readonly ulong[] CombinedDivisorsEnding3Squares;
private static readonly ulong[] CombinedDivisorsEnding7Squares;
private static readonly ulong[] CombinedDivisorsEnding9Squares;
private static readonly ulong[] HeuristicSmallCycleSnapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
internal enum CombinedDivisorPattern : byte
{
TwoAOneB = 0,
OneAOneB = 1,
ThreeAOneB = 2,
ThreeATwoB = 3,
}
internal enum HeuristicDivisorGroup : byte
{
None = 0,
GroupAConstant = 1,
GroupAWheel = 2,
GroupB = 3,
}
internal readonly struct HeuristicDivisorCandidate(ulong value, HeuristicDivisorGroup group, byte ending, byte priorityIndex, ushort wheelResidue)
{
public readonly ulong Value = value;
public readonly HeuristicDivisorGroup Group = group;
public readonly byte Ending = ending;
public readonly byte PriorityIndex = priorityIndex;
public readonly ushort WheelResidue = wheelResidue;
}
internal readonly struct HeuristicDivisorPreparation(
in HeuristicDivisorCandidate candidate,
in MontgomeryDivisorData divisorData,
ulong cycleLengthHint,
bool hasCycleLengthHint)
{
public readonly HeuristicDivisorCandidate Candidate = candidate;
public readonly MontgomeryDivisorData DivisorData = divisorData;
public readonly ulong CycleLengthHint = cycleLengthHint;
public readonly bool HasCycleLengthHint = hasCycleLengthHint;
public readonly bool RequiresCycleComputation = !hasCycleLengthHint;
}
[ThreadStatic]
private static HeuristicCombinedPrimeTester? _tester;
public static HeuristicCombinedPrimeTester Exclusive => _tester ??= new();
public static bool IsPrimeCpu(ulong n)
{
byte nMod10 = (byte)n.Mod10();
ulong maxDivisorSquare = ComputeHeuristicDivisorSquareLimit(n);
if (maxDivisorSquare < 9UL)
{
return EvaluateWithOpenNumericFallback(n);
}
return HeuristicTrialDivisionCpu(n, maxDivisorSquare, nMod10);
}
public static bool IsPrimeGpu(ulong n)
{
byte nMod10 = (byte)n.Mod10();
ulong maxDivisorSquare = ComputeHeuristicDivisorSquareLimit(n);
return IsPrimeGpu(n, maxDivisorSquare, nMod10);
}
public static bool IsPrimeGpu(ulong n, ulong maxDivisorSquare, byte nMod10)
{
// TODO: Is this condition ever met on the execution path in EvenPerfectBitScanner?
if (maxDivisorSquare < 9UL)
{
return EvaluateWithOpenNumericFallback(n);
}
bool compositeDetected = HeuristicTrialDivisionGpuDetectsDivisor(n, maxDivisorSquare, nMod10);
return !compositeDetected;
}
private static bool HeuristicTrialDivisionCpu(ulong n, ulong maxDivisorSquare, byte nMod10)
{
ReadOnlySpan<ulong> combinedDivisors = GetCombinedDivisors(nMod10);
ReadOnlySpan<ulong> combinedDivisorSquares = GetCombinedDivisorSquares(nMod10);
int length = combinedDivisors.Length;
for (int i = 0; i < length; i++)
{
ulong divisorSquare = combinedDivisorSquares[i];
if (divisorSquare > maxDivisorSquare)
{
break;
}
if (n % combinedDivisors[i] == 0UL)
{
return false;
}
}
return EvaluateWithOpenNumericFallback(n);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int GetBatchSize(ulong n) => n switch
{
< 1_000 => 8,
< 10_000 => 16,
< 100_000 => 32,
< 1_000_000 => 128,
_ => 256,
};
private static bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong maxDivisorSquare, byte nMod10)
{
GpuPrimeWorkLimiter.Acquire();
var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent(1);
var accelerator = gpu.Accelerator;
var kernel = PrimeTester.PrimeTesterGpuContextPool.PrimeTesterGpuContextLease.HeuristicTrialDivisionKernel;
var flagView1D = gpu.HeuristicFlag.View;
var flagView = flagView1D.AsContiguous();
bool compositeDetected = false;
int divisorLength = GetCombinedDivisors(nMod10).Length;
int compositeFlag = 0;
flagView1D.CopyFromCPU(ref compositeFlag, 1);
kernel(
divisorLength,
flagView,
n,
maxDivisorSquare,
HeuristicGpuDivisorTableKind.Combined,
gpu.HeuristicGpuTables);
accelerator.Synchronize();
flagView1D.CopyToCPU(ref compositeFlag, 1);
compositeDetected = compositeFlag != 0;
gpu.Dispose();
GpuPrimeWorkLimiter.Release();
return compositeDetected;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool EvaluateWithOpenNumericFallback(ulong n)
{
return Prime.Numbers.IsPrime(n);
}
internal static ulong ComputeHeuristicDivisorSquareLimit(ulong n)
{
return n;
}
private static ReadOnlySpan<ulong> GetCombinedDivisors(byte nMod10)
{
return nMod10 switch
{
1 => CombinedDivisorsEnding1,
3 => CombinedDivisorsEnding3,
7 => CombinedDivisorsEnding7,
9 => CombinedDivisorsEnding9,
_ => throw new InvalidOperationException($"Unsupported combined divisor selector for digit {nMod10}."),
};
}
private static ReadOnlySpan<ulong> GetCombinedDivisorSquares(byte nMod10)
{
return nMod10 switch
{
1 => CombinedDivisorsEnding1Squares,
3 => CombinedDivisorsEnding3Squares,
7 => CombinedDivisorsEnding7Squares,
9 => CombinedDivisorsEnding9Squares,
_ => throw new InvalidOperationException($"Unsupported combined divisor square selector for digit {nMod10}."),
};
}
internal static ReadOnlySpan<ulong> GetCombinedDivisors(byte nMod10, CombinedDivisorPattern pattern)
{
return pattern switch
{
CombinedDivisorPattern.TwoAOneB => nMod10 switch
{
1 => CombinedDivisorsEnding1TwoAOneB,
3 => CombinedDivisorsEnding3TwoAOneB,
7 => CombinedDivisorsEnding7TwoAOneB,
9 => CombinedDivisorsEnding9TwoAOneB,
_ => throw new InvalidOperationException($"Unsupported combined divisor selector for digit {nMod10}."),
},
CombinedDivisorPattern.OneAOneB => nMod10 switch
{
1 => CombinedDivisorsEnding1OneAOneB,
3 => CombinedDivisorsEnding3OneAOneB,
7 => CombinedDivisorsEnding7OneAOneB,
9 => CombinedDivisorsEnding9OneAOneB,
_ => throw new InvalidOperationException($"Unsupported combined divisor selector for digit {nMod10}."),
},
CombinedDivisorPattern.ThreeAOneB => nMod10 switch
{
1 => CombinedDivisorsEnding1ThreeAOneB,
3 => CombinedDivisorsEnding3ThreeAOneB,
7 => CombinedDivisorsEnding7ThreeAOneB,
9 => CombinedDivisorsEnding9ThreeAOneB,
_ => throw new InvalidOperationException($"Unsupported combined divisor selector for digit {nMod10}."),
},
CombinedDivisorPattern.ThreeATwoB => nMod10 switch
{
1 => CombinedDivisorsEnding1ThreeATwoB,
3 => CombinedDivisorsEnding3ThreeATwoB,
7 => CombinedDivisorsEnding7ThreeATwoB,
9 => CombinedDivisorsEnding9ThreeATwoB,
_ => throw new InvalidOperationException($"Unsupported combined divisor selector for digit {nMod10}."),
},
_ => throw new InvalidOperationException($"Unsupported combined divisor pattern: {pattern}."),
};
}
internal static ReadOnlySpan<ulong> GetCombinedDivisorSquares(byte nMod10, CombinedDivisorPattern pattern)
{
return pattern switch
{
CombinedDivisorPattern.TwoAOneB => nMod10 switch
{
1 => CombinedDivisorsEnding1TwoAOneBSquares,
3 => CombinedDivisorsEnding3TwoAOneBSquares,
7 => CombinedDivisorsEnding7TwoAOneBSquares,
9 => CombinedDivisorsEnding9TwoAOneBSquares,
_ => throw new InvalidOperationException($"Unsupported combined divisor square selector for digit {nMod10}."),
},
CombinedDivisorPattern.OneAOneB => nMod10 switch
{
1 => CombinedDivisorsEnding1OneAOneBSquares,
3 => CombinedDivisorsEnding3OneAOneBSquares,
7 => CombinedDivisorsEnding7OneAOneBSquares,
9 => CombinedDivisorsEnding9OneAOneBSquares,
_ => throw new InvalidOperationException($"Unsupported combined divisor square selector for digit {nMod10}."),
},
CombinedDivisorPattern.ThreeAOneB => nMod10 switch
{
1 => CombinedDivisorsEnding1ThreeAOneBSquares,
3 => CombinedDivisorsEnding3ThreeAOneBSquares,
7 => CombinedDivisorsEnding7ThreeAOneBSquares,
9 => CombinedDivisorsEnding9ThreeAOneBSquares,
_ => throw new InvalidOperationException($"Unsupported combined divisor square selector for digit {nMod10}."),
},
CombinedDivisorPattern.ThreeATwoB => nMod10 switch
{
1 => CombinedDivisorsEnding1ThreeATwoBSquares,
3 => CombinedDivisorsEnding3ThreeATwoBSquares,
7 => CombinedDivisorsEnding7ThreeATwoBSquares,
9 => CombinedDivisorsEnding9ThreeATwoBSquares,
_ => throw new InvalidOperationException($"Unsupported combined divisor square selector for digit {nMod10}."),
},
_ => throw new InvalidOperationException($"Unsupported combined divisor pattern for squares: {pattern}."),
};
}
private static ulong[] BuildCombinedDivisors(byte nMod10, CombinedDivisorPattern pattern, out ulong[] squares)
{
ReadOnlySpan<ulong> groupA = HeuristicPrimeSieves.GroupADivisors;
ReadOnlySpan<uint> groupB = GetGroupBDivisors(nMod10);
var combined = new List<ulong>(groupA.Length + groupB.Length);
int groupAIndex = 0;
while (groupAIndex < groupA.Length && groupA[groupAIndex] <= 19)
{
combined.Add(groupA[groupAIndex]);
groupAIndex++;
}
int groupBIndex = GetGroupBStartIndex(groupB);
int groupAInterleaveCount;
int groupBInterleaveCount;
switch (pattern)
{
case CombinedDivisorPattern.OneAOneB:
groupAInterleaveCount = 1;
groupBInterleaveCount = 1;
break;
case CombinedDivisorPattern.TwoAOneB:
groupAInterleaveCount = 2;
groupBInterleaveCount = 1;
break;
case CombinedDivisorPattern.ThreeAOneB:
groupAInterleaveCount = 3;
groupBInterleaveCount = 1;
break;
case CombinedDivisorPattern.ThreeATwoB:
groupAInterleaveCount = 3;
groupBInterleaveCount = 2;
break;
default:
groupAInterleaveCount = 2;
groupBInterleaveCount = 1;
break;
}
while (groupAIndex < groupA.Length || groupBIndex < groupB.Length)
{
int addedA = 0;
while (addedA < groupAInterleaveCount && groupAIndex < groupA.Length)
{
combined.Add(groupA[groupAIndex]);
groupAIndex++;
addedA++;
}
int addedB = 0;
while (addedB < groupBInterleaveCount && groupBIndex < groupB.Length)
{
combined.Add(groupB[groupBIndex]);
groupBIndex++;
addedB++;
}
}
ulong[] result = [.. combined];
ulong[] resultSquares = new ulong[result.Length];
for (int i = 0; i < result.Length; i++)
{
ulong divisor = result[i];
resultSquares[i] = divisor * divisor;
}
squares = resultSquares;
return result;
}
private static int GetGroupBStartIndex(ReadOnlySpan<uint> divisors)
{
int index = 0;
while (index < divisors.Length && IsGroupAPrefixValue(divisors[index]))
{
index++;
}
return index;
}
private static bool IsGroupAPrefixValue(ulong value) => value is 3UL or 7UL or 11UL or 13UL;
private static HeuristicDivisorGroup ResolveGroup(ulong value) => value switch
{
3UL or 7UL or 11UL or 13UL => HeuristicDivisorGroup.GroupAConstant,
_ when value % 10UL == 3UL => HeuristicDivisorGroup.GroupAWheel,
_ => HeuristicDivisorGroup.GroupB,
};
private static ReadOnlySpan<uint> GetGroupBDivisors(byte ending) => ending switch
{
1 => DivisorGenerator.SmallPrimesLastOneWithoutLastThree,
3 => [],
7 => DivisorGenerator.SmallPrimesLastSevenWithoutLastThree,
9 => DivisorGenerator.SmallPrimesLastNineWithoutLastThree,
_ => [],
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static HeuristicDivisorEnumerator CreateHeuristicDivisorEnumerator(ulong maxDivisorSquare, byte nMod10, Span<HeuristicGroupBSequenceState> groupBBuffer)
{
return new HeuristicDivisorEnumerator(maxDivisorSquare, nMod10, groupBBuffer);
}
internal struct HeuristicGroupBSequenceState
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static MersenneHeuristicDivisorEnumerator CreateMersenneDivisorEnumerator(ulong exponent, ulong maxDivisor)
{
return new MersenneHeuristicDivisorEnumerator(exponent, maxDivisor);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static HeuristicDivisorPreparation PrepareHeuristicDivisor(in HeuristicDivisorCandidate candidate)
{
ulong divisor = candidate.Value;
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
bool hasCycleHint = TryGetCycleLengthHint(divisor, out ulong cycleLength);
return new HeuristicDivisorPreparation(candidate, divisorData, cycleLength, hasCycleHint);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ulong ResolveHeuristicCycleLength(
ulong exponent,
in HeuristicDivisorPreparation preparation,
out bool cycleFromHint,
out bool cycleComputed,
out bool primeOrderFailed)
{
if (preparation.HasCycleLengthHint && preparation.CycleLengthHint != 0UL)
{
cycleFromHint = true;
cycleComputed = true;
primeOrderFailed = false;
return preparation.CycleLengthHint;
}
ulong divisor = preparation.Candidate.Value;
MontgomeryDivisorData divisorData = preparation.DivisorData;
bool trySuccess = MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
divisor,
exponent,
divisorData,
out ulong computedCycle,
out bool primeOrderFailedLocal);
if (trySuccess && computedCycle != 0UL)
{
cycleFromHint = false;
cycleComputed = true;
primeOrderFailed = primeOrderFailedLocal;
return computedCycle;
}
primeOrderFailed = primeOrderFailedLocal || !trySuccess || computedCycle == 0UL;
ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
cycleFromHint = false;
cycleComputed = resolvedCycle != 0UL;
return resolvedCycle;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool TryGetCycleLengthHint(ulong divisor, out ulong cycleLength)
{
ulong[] snapshot = HeuristicSmallCycleSnapshot;
if (divisor < (ulong)snapshot.Length)
{
cycleLength = snapshot[(int)divisor];
if (cycleLength != 0UL)
{
return true;
}
}
cycleLength = 0UL;
return false;
}
internal ref struct HeuristicDivisorEnumerator
{
private readonly ulong maxDivisorSquare;
private readonly ReadOnlySpan<ulong> combinedDivisors;
private readonly ReadOnlySpan<ulong> combinedDivisorSquares;
private int index;
public HeuristicDivisorEnumerator(ulong maxDivisorSquare, byte nMod10, Span<HeuristicGroupBSequenceState> groupBBuffer)
{
this.maxDivisorSquare = maxDivisorSquare;
_ = groupBBuffer;
combinedDivisors = GetCombinedDivisors(nMod10);
combinedDivisorSquares = GetCombinedDivisorSquares(nMod10);
index = 0;
}
public bool TryGetNext(out HeuristicDivisorCandidate candidate)
{
while (index < combinedDivisors.Length)
{
int currentIndex = index;
ulong divisorSquare = combinedDivisorSquares[currentIndex];
if (divisorSquare > maxDivisorSquare)
{
index = combinedDivisors.Length;
break;
}
ulong entry = combinedDivisors[currentIndex];
index = currentIndex + 1;
HeuristicDivisorGroup group = ResolveGroup(entry);
ulong value = entry;
byte ending = (byte)(value % 10UL);
ushort residue = (ushort)(value % Wheel210);
candidate = new HeuristicDivisorCandidate(value, group, ending, 0, residue);
return true;
}
candidate = default;
return false;
}
}
internal ref struct MersenneHeuristicDivisorEnumerator
{
private readonly GpuUInt128 step;
private readonly GpuUInt128 limit;
private GpuUInt128 current;
private MersenneDivisorResidueStepper residueStepper;
private bool active;
private ulong processedCount;
private ulong lastDivisor;
public MersenneHeuristicDivisorEnumerator(ulong exponent, ulong maxDivisor)
{
var stepLocal = new GpuUInt128(exponent);
stepLocal.ShiftLeft(1);
step = stepLocal;
var limitLocal = new GpuUInt128(maxDivisor);
limit = limitLocal;
var firstDivisor = stepLocal;
firstDivisor.Add(1UL);
bool hasCandidates = !stepLocal.IsZero && firstDivisor.CompareTo(limitLocal) <= 0;
current = hasCandidates ? firstDivisor : GpuUInt128.Zero;
residueStepper = hasCandidates ? new MersenneDivisorResidueStepper(exponent, stepLocal, firstDivisor) : default;
active = hasCandidates;
processedCount = 0UL;
lastDivisor = 0UL;
}
public bool TryGetNext(out HeuristicDivisorCandidate candidate)
{
while (active)
{
ulong value = current.Low;
processedCount++;
lastDivisor = value;
bool admissible = residueStepper.IsAdmissible();
Advance();
if (admissible)
{
candidate = CreateCandidate(value);
return true;
}
}
candidate = default;
return false;
}
private void Advance()
{
GpuUInt128 next = current;
next.Add(step);
if (next.CompareTo(limit) > 0 || next.CompareTo(current) <= 0)
{
active = false;
return;
}
current = next;
residueStepper.Advance();
}
public readonly ulong ProcessedCount => processedCount;
public readonly ulong LastDivisor => lastDivisor;
public readonly bool Exhausted => !active;
private static HeuristicDivisorCandidate CreateCandidate(ulong value)
{
byte ending = (byte)(value % 10UL);
HeuristicDivisorGroup group;
if (value == 3UL || value == 7UL || value == 11UL || value == 13UL)
{
group = HeuristicDivisorGroup.GroupAConstant;
}
else if (ending == 3)
{
group = HeuristicDivisorGroup.GroupAWheel;
}
else
{
group = HeuristicDivisorGroup.GroupB;
}
ushort residue = (ushort)(value % Wheel210);
return new HeuristicDivisorCandidate(value, group, ending, 0, residue);
}
}
}
+20 -19
View File
@@ -15,19 +15,19 @@ public static class HeuristicPrimeSieves
private static readonly ushort[] GroupBResiduesEnding7 = [17, 37, 47, 67, 97, 107, 127, 137, 157, 167, 187, 197]; private static readonly ushort[] GroupBResiduesEnding7 = [17, 37, 47, 67, 97, 107, 127, 137, 157, 167, 187, 197];
private static readonly ushort[] GroupBResiduesEnding9 = [19, 29, 59, 79, 89, 109, 139, 149, 169, 179, 199, 209]; private static readonly ushort[] GroupBResiduesEnding9 = [19, 29, 59, 79, 89, 109, 139, 149, 169, 179, 199, 209];
private static readonly int[] GroupADivisorsStorage; private static readonly ulong[] GroupADivisorsStorage;
private static readonly ulong[] GroupADivisorSquaresStorage; private static readonly ulong[] GroupADivisorSquaresStorage;
private static readonly int[] GroupBDivisorsEnding1Storage; private static readonly ulong[] GroupBDivisorsEnding1Storage;
private static readonly ulong[] GroupBDivisorSquaresEnding1Storage; private static readonly ulong[] GroupBDivisorSquaresEnding1Storage;
private static readonly int[] GroupBDivisorsEnding3Storage; private static readonly ulong[] GroupBDivisorsEnding3Storage;
private static readonly ulong[] GroupBDivisorSquaresEnding3Storage; private static readonly ulong[] GroupBDivisorSquaresEnding3Storage;
private static readonly int[] GroupBDivisorsEnding7Storage; private static readonly ulong[] GroupBDivisorsEnding7Storage;
private static readonly ulong[] GroupBDivisorSquaresEnding7Storage; private static readonly ulong[] GroupBDivisorSquaresEnding7Storage;
private static readonly int[] GroupBDivisorsEnding9Storage; private static readonly ulong[] GroupBDivisorsEnding9Storage;
private static readonly ulong[] GroupBDivisorSquaresEnding9Storage; private static readonly ulong[] GroupBDivisorSquaresEnding9Storage;
static HeuristicPrimeSieves() static HeuristicPrimeSieves()
@@ -46,45 +46,46 @@ public static class HeuristicPrimeSieves
// Intentionally left blank. Accessing this method forces the static constructor to run. // Intentionally left blank. Accessing this method forces the static constructor to run.
} }
public static ReadOnlySpan<int> GroupADivisors => GroupADivisorsStorage; public static ReadOnlySpan<ulong> GroupADivisors => GroupADivisorsStorage;
public static ReadOnlySpan<ulong> GroupADivisorSquares => GroupADivisorSquaresStorage; public static ReadOnlySpan<ulong> GroupADivisorSquares => GroupADivisorSquaresStorage;
public static ReadOnlySpan<int> GroupBDivisorsEnding1 => GroupBDivisorsEnding1Storage; public static ReadOnlySpan<ulong> GroupBDivisorsEnding1 => GroupBDivisorsEnding1Storage;
public static ReadOnlySpan<ulong> GroupBDivisorSquaresEnding1 => GroupBDivisorSquaresEnding1Storage; public static ReadOnlySpan<ulong> GroupBDivisorSquaresEnding1 => GroupBDivisorSquaresEnding1Storage;
public static ReadOnlySpan<int> GroupBDivisorsEnding3 => GroupBDivisorsEnding3Storage; public static ReadOnlySpan<ulong> GroupBDivisorsEnding3 => GroupBDivisorsEnding3Storage;
public static ReadOnlySpan<ulong> GroupBDivisorSquaresEnding3 => GroupBDivisorSquaresEnding3Storage; public static ReadOnlySpan<ulong> GroupBDivisorSquaresEnding3 => GroupBDivisorSquaresEnding3Storage;
public static ReadOnlySpan<int> GroupBDivisorsEnding7 => GroupBDivisorsEnding7Storage; public static ReadOnlySpan<ulong> GroupBDivisorsEnding7 => GroupBDivisorsEnding7Storage;
public static ReadOnlySpan<ulong> GroupBDivisorSquaresEnding7 => GroupBDivisorSquaresEnding7Storage; public static ReadOnlySpan<ulong> GroupBDivisorSquaresEnding7 => GroupBDivisorSquaresEnding7Storage;
public static ReadOnlySpan<int> GroupBDivisorsEnding9 => GroupBDivisorsEnding9Storage; public static ReadOnlySpan<ulong> GroupBDivisorsEnding9 => GroupBDivisorsEnding9Storage;
public static ReadOnlySpan<ulong> GroupBDivisorSquaresEnding9 => GroupBDivisorSquaresEnding9Storage; public static ReadOnlySpan<ulong> GroupBDivisorSquaresEnding9 => GroupBDivisorSquaresEnding9Storage;
private static (int[] divisors, ulong[] squares) BuildGroupADivisors() private static (ulong[] divisors, ulong[] squares) BuildGroupADivisors()
{ {
var divisors = new int[SieveLength]; var divisors = new ulong[SieveLength];
var squares = new ulong[SieveLength]; var squares = new ulong[SieveLength];
int index = 0; int index = 0;
for (; index < GroupAConstants.Length && index < divisors.Length; index++) for (; index < GroupAConstants.Length && index < divisors.Length; index++)
{ {
int value = GroupAConstants[index]; ulong value = (ulong)GroupAConstants[index];
divisors[index] = value; divisors[index] = value;
squares[index] = (ulong)value * (ulong)value; squares[index] = value * value;
} }
int candidate = 23; int candidate = 23;
int incrementIndex = 0; int incrementIndex = 0;
while (index < divisors.Length) while (index < divisors.Length)
{ {
divisors[index] = candidate; ulong value = (ulong)candidate;
squares[index] = (ulong)candidate * (ulong)candidate; divisors[index] = value;
squares[index] = value * value;
candidate += GroupAIncrementPattern[incrementIndex]; candidate += GroupAIncrementPattern[incrementIndex];
incrementIndex ^= 1; incrementIndex ^= 1;
@@ -94,9 +95,9 @@ public static class HeuristicPrimeSieves
return (divisors, squares); return (divisors, squares);
} }
private static (int[] divisors, ulong[] squares) BuildGroupBResidueSieve(ReadOnlySpan<ushort> residues) private static (ulong[] divisors, ulong[] squares) BuildGroupBResidueSieve(ReadOnlySpan<ushort> residues)
{ {
var divisors = new int[SieveLength]; var divisors = new ulong[SieveLength];
var squares = new ulong[SieveLength]; var squares = new ulong[SieveLength];
int index = 0; int index = 0;
@@ -112,7 +113,7 @@ public static class HeuristicPrimeSieves
continue; continue;
} }
divisors[index] = (int)candidate; divisors[index] = candidate;
squares[index] = candidate * candidate; squares[index] = candidate * candidate;
index++; index++;
} }
+193 -342
View File
@@ -1,13 +1,5 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using Open.Numeric.Primes; using Open.Numeric.Primes;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using ILGPU;
using ILGPU.Algorithms;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
@@ -21,8 +13,6 @@ public sealed class HeuristicPrimeTester
} }
// TODO: Don't use these tiny arrays. Hard-code the values / checks instead // TODO: Don't use these tiny arrays. Hard-code the values / checks instead
private static readonly ulong[] GroupAConstantDivisors = [3UL, 7UL, 11UL, 13UL];
private static readonly byte[] GroupAIncrementPattern = [20, 10];
private static readonly byte[] GroupBEndingOrderMod1 = [9, 1]; private static readonly byte[] GroupBEndingOrderMod1 = [9, 1];
private static readonly byte[] GroupBEndingOrderMod3 = [9, 7]; private static readonly byte[] GroupBEndingOrderMod3 = [9, 7];
private static readonly byte[] GroupBEndingOrderMod7 = [7, 1]; private static readonly byte[] GroupBEndingOrderMod7 = [7, 1];
@@ -30,10 +20,8 @@ public sealed class HeuristicPrimeTester
// TODO: Would it speed up things if we implement something like ExponentRemainderStepper, // TODO: Would it speed up things if we implement something like ExponentRemainderStepper,
// MersenneDivisorResidueStepper, or CycleRemainderStepper work with these? // MersenneDivisorResidueStepper, or CycleRemainderStepper work with these?
private static readonly ushort[] Wheel210ResiduesEnding1 = [1, 11, 31, 41, 61, 71, 101, 121, 131, 151, 181, 191];
private static readonly ushort[] Wheel210ResiduesEnding7 = [17, 37, 47, 67, 97, 107, 127, 137, 157, 167, 187, 197];
private static readonly ushort[] Wheel210ResiduesEnding9 = [19, 29, 59, 79, 89, 109, 139, 149, 169, 179, 199, 209];
private const ulong Wheel210 = 210UL; private const ulong Wheel210 = 210UL;
private const int GroupAConstantCount = 4;
internal const int MaxGroupBSequences = 36; internal const int MaxGroupBSequences = 36;
private static readonly bool UseHeuristicGroupBTrialDivision = false; // Temporary fallback gate for Group B. private static readonly bool UseHeuristicGroupBTrialDivision = false; // Temporary fallback gate for Group B.
@@ -72,85 +60,78 @@ public sealed class HeuristicPrimeTester
} }
public static int HeuristicGpuDivisorBatchSize { get; set; } = 4_096;
public static int HeuristicDivisorInterleaveBatchSize { get; set; } = 64;
[ThreadStatic] [ThreadStatic]
private static HeuristicPrimeTester? _tester; private static HeuristicPrimeTester? _tester;
public static HeuristicPrimeTester Exclusive => _tester ??= new(); public static HeuristicPrimeTester Exclusive => _tester ??= new();
public bool IsPrimeCpu(ulong n, CancellationToken ct) public bool IsPrimeCpu(ulong n)
{ {
ct.ThrowIfCancellationRequested();
byte nMod10 = (byte)n.Mod10(); byte nMod10 = (byte)n.Mod10();
ulong sqrtLimit = ComputeHeuristicSqrt(n); ulong maxDivisorSquare = ComputeHeuristicDivisorSquareLimit(n);
if (sqrtLimit < 3UL) if (maxDivisorSquare < 9UL)
{ {
return EvaluateWithOpenNumericFallback(n); return EvaluateWithOpenNumericFallback(n);
} }
bool includeGroupB = UseHeuristicGroupBTrialDivision; bool includeGroupB = UseHeuristicGroupBTrialDivision;
return HeuristicTrialDivisionCpu(n, sqrtLimit, nMod10, includeGroupB); return HeuristicTrialDivisionCpu(n, maxDivisorSquare, nMod10, includeGroupB);
}
public bool IsPrimeCpu(ulong n)
{
return IsPrimeCpu(n, CancellationToken.None);
} }
public bool IsPrimeGpu(ulong n) public bool IsPrimeGpu(ulong n)
{ {
byte nMod10 = (byte)n.Mod10(); byte nMod10 = (byte)n.Mod10();
ulong sqrtLimit = ComputeHeuristicSqrt(n); ulong maxDivisorSquare = ComputeHeuristicDivisorSquareLimit(n);
return IsPrimeGpu(n, sqrtLimit, nMod10); return IsPrimeGpu(n, maxDivisorSquare, nMod10);
} }
public bool IsPrimeGpu(ulong n, ulong sqrtLimit, byte nMod10) public bool IsPrimeGpu(ulong n, ulong maxDivisorSquare, byte nMod10)
{ {
return HeuristicIsPrimeGpuCore(n, sqrtLimit, nMod10); return HeuristicIsPrimeGpuCore(n, maxDivisorSquare, nMod10);
} }
private bool HeuristicIsPrimeGpuCore(ulong n, ulong sqrtLimit, byte nMod10) private bool HeuristicIsPrimeGpuCore(ulong n, ulong maxDivisorSquare, byte nMod10)
{ {
// TODO: Is this condition ever met on the execution path in EvenPerfectBitScanner? // TODO: Is this condition ever met on the execution path in EvenPerfectBitScanner?
if (sqrtLimit < 3UL) if (maxDivisorSquare < 9UL)
{ {
return EvaluateWithOpenNumericFallback(n); return EvaluateWithOpenNumericFallback(n);
} }
if (!UseHeuristicGroupBTrialDivision) // if (!UseHeuristicGroupBTrialDivision)
{ // {
return HeuristicTrialDivisionCpu(n, sqrtLimit, nMod10, includeGroupB: false); // return HeuristicTrialDivisionCpu(n, maxDivisorSquare, nMod10, includeGroupB: false);
} // }
bool compositeDetected = HeuristicTrialDivisionGpuDetectsDivisor(n, sqrtLimit, nMod10); bool compositeDetected = HeuristicTrialDivisionGpuDetectsDivisor(n, maxDivisorSquare, nMod10);
return !compositeDetected; return !compositeDetected;
} }
private static bool HeuristicTrialDivisionCpu(ulong n, ulong sqrtLimit, byte nMod10, bool includeGroupB) private static bool HeuristicTrialDivisionCpu(ulong n, ulong maxDivisorSquare, byte nMod10, bool includeGroupB)
{ {
ReadOnlySpan<int> groupADivisors = HeuristicPrimeSieves.GroupADivisors; ReadOnlySpan<ulong> groupADivisors = HeuristicPrimeSieves.GroupADivisors;
int interleaveBatchSize = Math.Max(1, HeuristicDivisorInterleaveBatchSize); ReadOnlySpan<ulong> groupADivisorSquares = HeuristicPrimeSieves.GroupADivisorSquares;
int interleaveBatchSize = HeuristicCombinedPrimeTester.GetBatchSize(n);
int groupAIndex = 0; int groupAIndex = 0;
if (!includeGroupB) if (!includeGroupB)
{ {
while (groupAIndex < groupADivisors.Length) int groupALength = groupADivisors.Length;
while (groupAIndex < groupALength)
{ {
int processed = 0; int processed = 0;
while (processed < interleaveBatchSize && groupAIndex < groupADivisors.Length) while (processed < interleaveBatchSize && groupAIndex < groupALength)
{ {
ulong divisor = (ulong)groupADivisors[groupAIndex]; ulong divisorSquare = groupADivisorSquares[groupAIndex];
if (divisor > sqrtLimit) if (divisorSquare > maxDivisorSquare)
{ {
groupAIndex = groupADivisors.Length; groupAIndex = groupALength;
break; break;
} }
ulong divisor = (ulong)groupADivisors[groupAIndex];
if (n % divisor == 0UL) if (n % divisor == 0UL)
{ {
return false; return false;
@@ -160,14 +141,14 @@ public sealed class HeuristicPrimeTester
processed++; processed++;
} }
if (groupAIndex >= groupADivisors.Length) if (groupAIndex >= groupALength)
{ {
break; break;
} }
if ((ulong)groupADivisors[groupAIndex] > sqrtLimit) if (groupADivisorSquares[groupAIndex] > maxDivisorSquare)
{ {
groupAIndex = groupADivisors.Length; groupAIndex = groupALength;
break; break;
} }
} }
@@ -185,8 +166,8 @@ public sealed class HeuristicPrimeTester
? stackalloc int[endingOrder.Length] ? stackalloc int[endingOrder.Length]
: new int[endingOrder.Length]; : new int[endingOrder.Length];
bool groupAHasMore = groupAIndex < groupADivisors.Length && (ulong)groupADivisors[groupAIndex] <= sqrtLimit; bool groupAHasMore = groupAIndex < groupADivisors.Length && groupADivisorSquares[groupAIndex] <= maxDivisorSquare;
bool groupBHasMore = HasGroupBCandidates(endingOrder, indices, sqrtLimit); bool groupBHasMore = HasGroupBCandidates(endingOrder, indices, maxDivisorSquare);
while (groupAHasMore || groupBHasMore) while (groupAHasMore || groupBHasMore)
{ {
@@ -195,14 +176,15 @@ public sealed class HeuristicPrimeTester
int processed = 0; int processed = 0;
while (processed < interleaveBatchSize && groupAIndex < groupADivisors.Length) while (processed < interleaveBatchSize && groupAIndex < groupADivisors.Length)
{ {
ulong divisor = (ulong)groupADivisors[groupAIndex]; ulong divisorSquare = groupADivisorSquares[groupAIndex];
if (divisor > sqrtLimit) if (divisorSquare > maxDivisorSquare)
{ {
groupAIndex = groupADivisors.Length; groupAIndex = groupADivisors.Length;
groupAHasMore = false; groupAHasMore = false;
break; break;
} }
ulong divisor = (ulong)groupADivisors[groupAIndex];
if (n % divisor == 0UL) if (n % divisor == 0UL)
{ {
return false; return false;
@@ -218,7 +200,7 @@ public sealed class HeuristicPrimeTester
} }
else else
{ {
groupAHasMore = (ulong)groupADivisors[groupAIndex] <= sqrtLimit; groupAHasMore = groupADivisorSquares[groupAIndex] <= maxDivisorSquare;
if (!groupAHasMore) if (!groupAHasMore)
{ {
groupAIndex = groupADivisors.Length; groupAIndex = groupADivisors.Length;
@@ -231,7 +213,7 @@ public sealed class HeuristicPrimeTester
int processed = 0; int processed = 0;
while (processed < interleaveBatchSize) while (processed < interleaveBatchSize)
{ {
if (!TrySelectNextGroupBDivisor(endingOrder, indices, sqrtLimit, out ulong divisor)) if (!TrySelectNextGroupBDivisor(endingOrder, indices, maxDivisorSquare, out ulong divisor))
{ {
groupBHasMore = false; groupBHasMore = false;
break; break;
@@ -247,7 +229,7 @@ public sealed class HeuristicPrimeTester
if (groupBHasMore) if (groupBHasMore)
{ {
groupBHasMore = HasGroupBCandidates(endingOrder, indices, sqrtLimit); groupBHasMore = HasGroupBCandidates(endingOrder, indices, maxDivisorSquare);
} }
} }
} }
@@ -256,200 +238,83 @@ public sealed class HeuristicPrimeTester
} }
private bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong maxDivisorSquare, byte nMod10)
private bool HeuristicTrialDivisionGpuDetectsDivisor(ulong n, ulong sqrtLimit, byte nMod10)
{ {
int batchCapacity = Math.Max(1, HeuristicGpuDivisorBatchSize); GpuPrimeWorkLimiter.Acquire();
int interleaveBatchSize = Math.Max(1, HeuristicDivisorInterleaveBatchSize); var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent(1);
var divisorPool = ThreadStaticPools.UlongPool;
var hitPool = ThreadStaticPools.BytePool;
ulong[] divisorArray = divisorPool.Rent(batchCapacity);
byte[] hitFlags = hitPool.Rent(batchCapacity);
var limiter = GpuPrimeWorkLimiter.Acquire();
var gpu = PrimeTester.PrimeTesterGpuContextPool.Rent();
var accelerator = gpu.Accelerator; var accelerator = gpu.Accelerator;
var state = PrimeTester.GpuKernelState.GetOrCreate(accelerator); var kernel = PrimeTester.PrimeTesterGpuContextPool.PrimeTesterGpuContextLease.HeuristicTrialDivisionKernel;
var scratch = state.RentScratch(batchCapacity, accelerator); var flagView1D = gpu.HeuristicFlag.View;
var flagView = flagView1D.AsContiguous();
bool compositeDetected = false; bool compositeDetected = false;
int count = 0; int compositeFlag = 0;
lock (gpu.ExecutionLock) int groupALength = HeuristicPrimeSieves.GroupADivisors.Length;
{
var input = scratch.Input;
var output = scratch.Output;
bool ProcessBatch(int length) flagView1D.CopyFromCPU(ref compositeFlag, 1);
{ kernel(
if (length <= 0) groupALength,
{ flagView,
return false; n,
} maxDivisorSquare,
HeuristicGpuDivisorTableKind.GroupA,
input.View.CopyFromCPU(ref divisorArray[0], length); gpu.HeuristicGpuTables);
state.HeuristicTrialDivisionKernel(length, input.View, n, output.View);
accelerator.Synchronize(); accelerator.Synchronize();
output.View.CopyToCPU(ref hitFlags[0], length); flagView1D.CopyToCPU(ref compositeFlag, 1);
compositeDetected = compositeFlag != 0;
for (int i = 0; i < length; i++) if (!compositeDetected)
{ {
if (hitFlags[i] == 0) ReadOnlySpan<byte> endingOrder = GetGroupBEndingOrder(nMod10);
for (int i = 0; i < endingOrder.Length && !compositeDetected; i++)
{
byte ending = endingOrder[i];
HeuristicGpuDivisorTableKind tableKind = ending switch
{
1 => HeuristicGpuDivisorTableKind.GroupBEnding1,
7 => HeuristicGpuDivisorTableKind.GroupBEnding7,
9 => HeuristicGpuDivisorTableKind.GroupBEnding9,
_ => HeuristicGpuDivisorTableKind.GroupA,
};
if (tableKind == HeuristicGpuDivisorTableKind.GroupA)
{ {
continue; continue;
} }
ulong divisor = divisorArray[i]; int divisorLength = GetGroupBDivisors(ending).Length;
if (divisor > 1UL && n % divisor == 0UL)
{ compositeFlag = 0;
return true; flagView1D.CopyFromCPU(ref compositeFlag, 1);
kernel(
divisorLength,
flagView,
n,
maxDivisorSquare,
tableKind,
gpu.HeuristicGpuTables);
accelerator.Synchronize();
flagView1D.CopyToCPU(ref compositeFlag, 1);
compositeDetected = compositeFlag != 0;
} }
} }
return false;
}
ReadOnlySpan<int> groupADivisors = HeuristicPrimeSieves.GroupADivisors;
int groupAIndex = 0;
ReadOnlySpan<byte> endingOrder = GetGroupBEndingOrder(nMod10);
Span<int> indices = endingOrder.Length <= 8
? stackalloc int[endingOrder.Length]
: new int[endingOrder.Length];
bool groupAHasMore = groupAIndex < groupADivisors.Length && (ulong)groupADivisors[groupAIndex] <= sqrtLimit;
bool groupBHasMore = !endingOrder.IsEmpty && HasGroupBCandidates(endingOrder, indices, sqrtLimit);
while (!compositeDetected && (groupAHasMore || groupBHasMore))
{
if (groupAHasMore)
{
int processed = 0;
while (processed < interleaveBatchSize && groupAIndex < groupADivisors.Length)
{
ulong divisor = (ulong)groupADivisors[groupAIndex];
if (divisor > sqrtLimit)
{
groupAIndex = groupADivisors.Length;
groupAHasMore = false;
break;
}
divisorArray[count] = divisor;
count++;
if (count == batchCapacity)
{
if (ProcessBatch(count))
{
compositeDetected = true;
goto Cleanup;
}
count = 0;
}
groupAIndex++;
processed++;
}
if (groupAIndex >= groupADivisors.Length)
{
groupAHasMore = false;
}
else
{
groupAHasMore = (ulong)groupADivisors[groupAIndex] <= sqrtLimit;
if (!groupAHasMore)
{
groupAIndex = groupADivisors.Length;
}
}
}
if (!compositeDetected && groupBHasMore)
{
int processed = 0;
while (processed < interleaveBatchSize)
{
if (!TrySelectNextGroupBDivisor(endingOrder, indices, sqrtLimit, out ulong divisor))
{
groupBHasMore = false;
break;
}
divisorArray[count] = divisor;
count++;
if (count == batchCapacity)
{
if (ProcessBatch(count))
{
compositeDetected = true;
goto Cleanup;
}
count = 0;
}
processed++;
}
if (groupBHasMore)
{
groupBHasMore = HasGroupBCandidates(endingOrder, indices, sqrtLimit);
}
}
}
if (!compositeDetected && count > 0)
{
if (ProcessBatch(count))
{
compositeDetected = true;
goto Cleanup;
}
count = 0;
}
}
Cleanup:
state.ReturnScratch(scratch);
divisorPool.Return(divisorArray);
hitPool.Return(hitFlags);
gpu.Dispose(); gpu.Dispose();
limiter.Dispose(); GpuPrimeWorkLimiter.Release();
return compositeDetected; return compositeDetected;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool EvaluateWithOpenNumericFallback(ulong n) private static bool EvaluateWithOpenNumericFallback(ulong n)
{ {
return Prime.Numbers.IsPrime(n); return Prime.Numbers.IsPrime(n);
} }
internal static ulong ComputeHeuristicDivisorSquareLimit(ulong n)
internal static ulong ComputeHeuristicSqrt(ulong n)
{ {
ulong sqrt = (ulong)Math.Sqrt(n); return n;
UInt128 square = (UInt128)sqrt * sqrt;
while (square > n)
{
sqrt--;
square = (UInt128)sqrt * sqrt;
}
ulong next = sqrt + 1UL;
UInt128 nextSquare = (UInt128)next * next;
while (nextSquare <= n)
{
sqrt = next;
next++;
nextSquare = (UInt128)next * next;
}
return sqrt;
} }
private static ReadOnlySpan<byte> GetGroupBEndingOrder(byte nMod10) => nMod10 switch private static ReadOnlySpan<byte> GetGroupBEndingOrder(byte nMod10) => nMod10 switch
@@ -461,29 +326,30 @@ Cleanup:
_ => ReadOnlySpan<byte>.Empty, _ => ReadOnlySpan<byte>.Empty,
}; };
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TrySelectNextGroupBDivisor(ReadOnlySpan<byte> endingOrder, Span<int> indices, ulong sqrtLimit, out ulong divisor) private static bool TrySelectNextGroupBDivisor(ReadOnlySpan<byte> endingOrder, Span<int> indices, ulong maxDivisorSquare, out ulong divisor)
{ {
ulong bestCandidate = ulong.MaxValue; ulong bestCandidate = ulong.MaxValue;
int bestEndingIndex = -1; int bestEndingIndex = -1;
for (int i = 0; i < endingOrder.Length; i++) for (int i = 0; i < endingOrder.Length; i++)
{ {
ReadOnlySpan<int> divisors = GetGroupBDivisors(endingOrder[i]); ReadOnlySpan<uint> divisors = GetGroupBDivisors(endingOrder[i]);
ReadOnlySpan<ulong> squares = GetGroupBDivisorSquares(endingOrder[i]);
int index = indices[i]; int index = indices[i];
if ((uint)index >= (uint)divisors.Length) if ((uint)index >= (uint)divisors.Length)
{ {
continue; continue;
} }
ulong candidate = (ulong)divisors[index]; ulong candidateSquare = squares[index];
if (candidate > sqrtLimit) if (candidateSquare > maxDivisorSquare)
{ {
indices[i] = divisors.Length; indices[i] = divisors.Length;
continue; continue;
} }
ulong candidate = divisors[index];
if (bestEndingIndex == -1 || candidate < bestCandidate) if (bestEndingIndex == -1 || candidate < bestCandidate)
{ {
bestCandidate = candidate; bestCandidate = candidate;
@@ -497,25 +363,26 @@ Cleanup:
return false; return false;
} }
divisor = bestCandidate; ReadOnlySpan<uint> selectedDivisors = GetGroupBDivisors(endingOrder[bestEndingIndex]);
divisor = selectedDivisors[indices[bestEndingIndex]];
indices[bestEndingIndex]++; indices[bestEndingIndex]++;
return true; return true;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool HasGroupBCandidates(ReadOnlySpan<byte> endingOrder, Span<int> indices, ulong sqrtLimit) private static bool HasGroupBCandidates(ReadOnlySpan<byte> endingOrder, Span<int> indices, ulong maxDivisorSquare)
{ {
for (int i = 0; i < endingOrder.Length; i++) for (int i = 0; i < endingOrder.Length; i++)
{ {
ReadOnlySpan<int> divisors = GetGroupBDivisors(endingOrder[i]); ReadOnlySpan<uint> divisors = GetGroupBDivisors(endingOrder[i]);
ReadOnlySpan<ulong> squares = GetGroupBDivisorSquares(endingOrder[i]);
int index = indices[i]; int index = indices[i];
if ((uint)index >= (uint)divisors.Length) if ((uint)index >= (uint)divisors.Length)
{ {
continue; continue;
} }
ulong candidate = (ulong)divisors[index]; if (squares[index] > maxDivisorSquare)
if (candidate > sqrtLimit)
{ {
indices[i] = divisors.Length; indices[i] = divisors.Length;
continue; continue;
@@ -527,44 +394,43 @@ Cleanup:
return false; return false;
} }
private static ReadOnlySpan<int> GetGroupBDivisors(byte ending) => ending switch
private static ReadOnlySpan<uint> GetGroupBDivisors(byte ending) => ending switch
{ {
1 => HeuristicPrimeSieves.GroupBDivisorsEnding1, 1 => DivisorGenerator.SmallPrimesLastOneWithoutLastThree,
3 => HeuristicPrimeSieves.GroupBDivisorsEnding3, 3 => ReadOnlySpan<uint>.Empty,
7 => HeuristicPrimeSieves.GroupBDivisorsEnding7, 7 => DivisorGenerator.SmallPrimesLastSevenWithoutLastThree,
9 => HeuristicPrimeSieves.GroupBDivisorsEnding9, 9 => DivisorGenerator.SmallPrimesLastNineWithoutLastThree,
_ => ReadOnlySpan<int>.Empty, _ => ReadOnlySpan<uint>.Empty,
}; };
private static ReadOnlySpan<ushort> GetWheel210ResiduesForEnding(byte ending) => ending switch private static ReadOnlySpan<ulong> GetGroupBDivisorSquares(byte ending) => ending switch
{ {
1 => Wheel210ResiduesEnding1, 1 => DivisorGenerator.SmallPrimesPow2LastOneWithoutLastThree,
7 => Wheel210ResiduesEnding7, 3 => ReadOnlySpan<ulong>.Empty,
9 => Wheel210ResiduesEnding9, 7 => DivisorGenerator.SmallPrimesPow2LastSevenWithoutLastThree,
_ => ReadOnlySpan<ushort>.Empty, 9 => DivisorGenerator.SmallPrimesPow2LastNineWithoutLastThree,
_ => ReadOnlySpan<ulong>.Empty,
}; };
private static int InitializeGroupBStates(byte nMod10, Span<HeuristicGroupBSequenceState> buffer) private static int InitializeGroupBStates(byte nMod10, Span<HeuristicGroupBSequenceState> buffer)
{ {
ReadOnlySpan<byte> endings = GetGroupBEndingOrder(nMod10); ReadOnlySpan<byte> endings = GetGroupBEndingOrder(nMod10);
int count = 0; int count = endings.Length;
for (int priorityIndex = 0; priorityIndex < endings.Length; priorityIndex++) for (int i = 0; i < count; i++)
{ {
ReadOnlySpan<ushort> residues = GetWheel210ResiduesForEnding(endings[priorityIndex]); buffer[i] = new HeuristicGroupBSequenceState(endings[i], (byte)i);
for (int residueIndex = 0; residueIndex < residues.Length; residueIndex++)
{
buffer[count++] = new HeuristicGroupBSequenceState(residues[residueIndex], endings[priorityIndex], (byte)priorityIndex);
}
} }
return count; return count;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static HeuristicDivisorEnumerator CreateHeuristicDivisorEnumerator(ulong sqrtLimit, byte nMod10, Span<HeuristicGroupBSequenceState> groupBBuffer) internal static HeuristicDivisorEnumerator CreateHeuristicDivisorEnumerator(ulong maxDivisorSquare, byte nMod10, Span<HeuristicGroupBSequenceState> groupBBuffer)
{ {
return new HeuristicDivisorEnumerator(sqrtLimit, nMod10, groupBBuffer); return new HeuristicDivisorEnumerator(maxDivisorSquare, nMod10, groupBBuffer);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -640,96 +506,69 @@ Cleanup:
return false; return false;
} }
internal ref struct HeuristicDivisorEnumerator internal ref struct HeuristicDivisorEnumerator
{ {
private readonly ulong sqrtLimit; private readonly ulong maxDivisorSquare;
private int groupAConstantIndex; private readonly ReadOnlySpan<ulong> groupADivisors;
private ulong groupAWheelCandidate; private readonly ReadOnlySpan<ulong> groupADivisorSquares;
private int groupAIncrementIndex; private int groupAIndex;
private bool groupAWheelActive;
private Span<HeuristicGroupBSequenceState> groupBStates; private Span<HeuristicGroupBSequenceState> groupBStates;
public HeuristicDivisorEnumerator(ulong sqrtLimit, byte nMod10, Span<HeuristicGroupBSequenceState> groupBBuffer) public HeuristicDivisorEnumerator(ulong maxDivisorSquare, byte nMod10, Span<HeuristicGroupBSequenceState> groupBBuffer)
{ {
this.sqrtLimit = sqrtLimit; this.maxDivisorSquare = maxDivisorSquare;
groupAConstantIndex = 0; groupADivisors = HeuristicPrimeSieves.GroupADivisors;
groupAWheelCandidate = 23UL; groupADivisorSquares = HeuristicPrimeSieves.GroupADivisorSquares;
groupAIncrementIndex = 0; groupAIndex = 0;
groupAWheelActive = sqrtLimit >= 23UL;
int count = InitializeGroupBStates(nMod10, groupBBuffer); int count = InitializeGroupBStates(nMod10, groupBBuffer);
groupBStates = count == 0 ? Span<HeuristicGroupBSequenceState>.Empty : groupBBuffer[..count]; groupBStates = count == 0 ? Span<HeuristicGroupBSequenceState>.Empty : groupBBuffer[..count];
for (int i = 0; i < groupBStates.Length; i++)
{
groupBStates[i].TryNormalize();
}
} }
public bool TryGetNext(out HeuristicDivisorCandidate candidate) public bool TryGetNext(out HeuristicDivisorCandidate candidate)
{ {
// TODO: Hard-code constant A divisor checks here instead of using an array while (groupAIndex < groupADivisors.Length)
while (groupAConstantIndex < GroupAConstantDivisors.Length)
{ {
ulong value = GroupAConstantDivisors[groupAConstantIndex++]; ulong divisorSquare = groupADivisorSquares[groupAIndex];
if (value > sqrtLimit) if (divisorSquare > maxDivisorSquare)
{ {
groupAConstantIndex = GroupAConstantDivisors.Length; groupAIndex = groupADivisors.Length;
break; break;
} }
// TODO: We're constantly dividing constants mod 10 which is constant too. int currentIndex = groupAIndex;
candidate = new HeuristicDivisorCandidate(value, HeuristicDivisorGroup.GroupAConstant, (byte)(value % 10UL), 0, (ushort)value); ulong divisor = groupADivisors[currentIndex];
groupAIndex++;
HeuristicDivisorGroup group = currentIndex < GroupAConstantCount
? HeuristicDivisorGroup.GroupAConstant
: HeuristicDivisorGroup.GroupAWheel;
candidate = new HeuristicDivisorCandidate(
divisor,
group,
(byte)(divisor % 10UL),
0,
(ushort)(divisor % Wheel210));
return true; return true;
} }
if (groupAWheelActive)
{
ulong value = groupAWheelCandidate;
if (value <= sqrtLimit)
{
candidate = new HeuristicDivisorCandidate(value, HeuristicDivisorGroup.GroupAWheel, 3, 0, (ushort)(value % Wheel210));
byte increment = GroupAIncrementPattern[groupAIncrementIndex];
groupAIncrementIndex ^= 1;
ulong next = value + increment;
if (next <= value)
{
groupAWheelActive = false;
}
else
{
groupAWheelCandidate = next;
}
return true;
}
groupAWheelActive = false;
}
ulong bestCandidate = ulong.MaxValue; ulong bestCandidate = ulong.MaxValue;
int bestIndex = -1; int bestIndex = -1;
for (int i = 0; i < groupBStates.Length; i++) for (int i = 0; i < groupBStates.Length; i++)
{ {
ref HeuristicGroupBSequenceState state = ref groupBStates[i]; ref HeuristicGroupBSequenceState state = ref groupBStates[i];
ulong value = state.Candidate; if (!state.TryPeek(maxDivisorSquare, out ulong candidateValue))
if (value == ulong.MaxValue)
{ {
continue; continue;
} }
if (value > sqrtLimit) if (bestIndex == -1 || candidateValue < bestCandidate ||
(candidateValue == bestCandidate && state.PriorityIndex < groupBStates[bestIndex].PriorityIndex))
{ {
state.Candidate = ulong.MaxValue; bestCandidate = candidateValue;
continue;
}
if (bestIndex == -1 || value < bestCandidate ||
(value == bestCandidate && state.PriorityIndex < groupBStates[bestIndex].PriorityIndex))
{
bestCandidate = value;
bestIndex = i; bestIndex = i;
} }
} }
@@ -741,57 +580,69 @@ Cleanup:
} }
ref HeuristicGroupBSequenceState bestState = ref groupBStates[bestIndex]; ref HeuristicGroupBSequenceState bestState = ref groupBStates[bestIndex];
var result = new HeuristicDivisorCandidate(bestState.Candidate, HeuristicDivisorGroup.GroupB, bestState.Ending, bestState.PriorityIndex, bestState.Residue);
bestState.Advance(); bestState.Advance();
candidate = result; candidate = new HeuristicDivisorCandidate(
bestCandidate,
HeuristicDivisorGroup.GroupB,
bestState.Ending,
bestState.PriorityIndex,
(ushort)(bestCandidate % Wheel210));
return true; return true;
} }
} }
internal struct HeuristicGroupBSequenceState(ushort residue, byte ending, byte priorityIndex)
{
public ulong Candidate = residue;
public byte PriorityIndex = priorityIndex;
public byte Ending = ending;
public ushort Residue = residue;
public void TryNormalize()
internal struct HeuristicGroupBSequenceState
{ {
ulong candidate = Candidate; public byte Ending;
if (candidate == ulong.MaxValue) public byte PriorityIndex;
public int Index;
public HeuristicGroupBSequenceState(byte ending, byte priorityIndex)
{ {
return; Ending = ending;
PriorityIndex = priorityIndex;
Index = 0;
} }
while (candidate <= 13UL) public bool TryPeek(ulong maxDivisorSquare, out ulong divisor)
{ {
ulong next = candidate + Wheel210; ReadOnlySpan<uint> divisors = GetGroupBDivisors(Ending);
if (next <= candidate) ReadOnlySpan<ulong> squares = GetGroupBDivisorSquares(Ending);
int index = Index;
if ((uint)index >= (uint)divisors.Length)
{ {
Candidate = ulong.MaxValue; divisor = 0UL;
return; return false;
} }
candidate = next; if (squares[index] > maxDivisorSquare)
{
Index = divisors.Length;
divisor = 0UL;
return false;
} }
Candidate = candidate; divisor = (ulong)divisors[index];
return true;
} }
public void Advance() public void Advance()
{ {
ulong candidate = Candidate; int next = Index + 1;
if (candidate == ulong.MaxValue) ReadOnlySpan<uint> divisors = GetGroupBDivisors(Ending);
if ((uint)next >= (uint)divisors.Length)
{ {
return; Index = divisors.Length;
}
else
{
Index = next;
}
}
} }
ulong next = candidate + Wheel210;
Candidate = next > candidate ? next : ulong.MaxValue;
}
}
internal ref struct MersenneHeuristicDivisorEnumerator internal ref struct MersenneHeuristicDivisorEnumerator
{ {
@@ -16,8 +16,10 @@ public interface IMersenneNumberDivisorByDivisorTester
void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues); void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues);
public interface IDivisorScanSession : IDisposable public interface IDivisorScanSession
{ {
void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits); void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits);
void Dispose();
} }
} }
+51 -45
View File
@@ -4,6 +4,7 @@ using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Runtime.InteropServices;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
@@ -136,20 +137,31 @@ public class MersenneDivisorCycles
{ {
// Binary pairs: (ulong divisor, ulong cycle) // Binary pairs: (ulong divisor, ulong cycle)
using var reader = new BinaryReader(compressor, Encoding.UTF8, leaveOpen: true); using var reader = new BinaryReader(compressor, Encoding.UTF8, leaveOpen: true);
Stream stream = reader.BaseStream;
byte[] buffer = new byte[16];
while (true) while (true)
{ {
ulong d, c; int read = 0;
try Span<byte> span = buffer;
while (read < span.Length)
{ {
d = reader.ReadUInt64(); int chunk = stream.Read(span[read..]);
c = reader.ReadUInt64(); if (chunk == 0)
} {
catch (EndOfStreamException) if (read == 0)
{ {
yield break; yield break;
} }
yield return (d, c); throw new EndOfStreamException("Incomplete divisor-cycle entry.");
}
read += chunk;
}
ulong divisor = BinaryPrimitives.ReadUInt64LittleEndian(span[..8]);
ulong cycle = BinaryPrimitives.ReadUInt64LittleEndian(span[8..]);
yield return (divisor, cycle);
} }
} }
@@ -379,7 +391,8 @@ public class MersenneDivisorCycles
for (int i = 0; i < smallLength; i++) for (int i = 0; i < smallLength; i++)
{ {
ulong prime = smallPrimes[i]; ulong prime = smallPrimes[i];
if (smallPrimesSquared[i] > remaining) ulong primeSquared = smallPrimesSquared[i];
if (primeSquared > remaining)
{ {
break; break;
} }
@@ -398,7 +411,9 @@ public class MersenneDivisorCycles
// Atomic.Add(ref _tryFactorIntoCountsInternalHits, 1UL); // Atomic.Add(ref _tryFactorIntoCountsInternalHits, 1UL);
// Console.WriteLine($"MersenneDivisorCycles.TryFactorIntoCountsInternal hits {Volatile.Read(ref _tryFactorIntoCountsInternalHits)}"); // Console.WriteLine($"MersenneDivisorCycles.TryFactorIntoCountsInternal hits {Volatile.Read(ref _tryFactorIntoCountsInternalHits)}");
if (PrimeTester.IsPrimeCpu(remaining, CancellationToken.None)) // HeuristicPrimeTester primeTester = _primeTester ??= new();
if (PrimeTester.IsPrimeGpu(remaining))
// if (PrimeTester.IsPrimeCpu(remaining, CancellationToken.None))
{ {
AddFactor(counts, remaining); AddFactor(counts, remaining);
return true; return true;
@@ -438,26 +453,14 @@ public class MersenneDivisorCycles
private static void AddFactor(Dictionary<ulong, int> counts, ulong factor) private static void AddFactor(Dictionary<ulong, int> counts, ulong factor)
{ {
if (counts.TryGetValue(factor, out int existing)) ref int entry = ref CollectionsMarshal.GetValueRefOrAddDefault(counts, factor, out bool exists);
{ entry = exists ? entry + 1 : 1;
counts[factor] = existing + 1;
}
else
{
counts[factor] = 1;
}
} }
private static void AddFactor(Dictionary<ulong, int> counts, ulong factor, int multiplicity) private static void AddFactor(Dictionary<ulong, int> counts, ulong factor, int multiplicity)
{ {
if (counts.TryGetValue(factor, out int existing)) ref int entry = ref CollectionsMarshal.GetValueRefOrAddDefault(counts, factor, out bool exists);
{ entry = exists ? entry + multiplicity : multiplicity;
counts[factor] = existing + multiplicity;
}
else
{
counts[factor] = multiplicity;
}
} }
private static ulong ReduceOrder(in MontgomeryDivisorData divisorData, ulong initialOrder, Dictionary<ulong, int> factorCounts) private static ulong ReduceOrder(in MontgomeryDivisorData divisorData, ulong initialOrder, Dictionary<ulong, int> factorCounts)
@@ -535,8 +538,8 @@ public class MersenneDivisorCycles
private static void ProcessReduceOrderPrime(Span<ulong> candidateBuffer, Span<bool> evaluationBuffer, ref ulong order, ulong prime, int multiplicity, ref ExponentRemainderStepperCpu stepper) private static void ProcessReduceOrderPrime(Span<ulong> candidateBuffer, Span<bool> evaluationBuffer, ref ulong order, ulong prime, int multiplicity, ref ExponentRemainderStepperCpu stepper)
{ {
ulong working = order; ulong working = order;
int actual = 0; int writeIndex = multiplicity;
while (actual < multiplicity) while (writeIndex > 0)
{ {
if (working < prime) if (working < prime)
{ {
@@ -549,26 +552,20 @@ public class MersenneDivisorCycles
break; break;
} }
candidateBuffer[actual] = reduced; writeIndex--;
candidateBuffer[writeIndex] = reduced;
working = reduced; working = reduced;
actual++;
} }
int actual = multiplicity - writeIndex;
if (actual == 0) if (actual == 0)
{ {
return; return;
} }
Span<ulong> candidates = candidateBuffer.Slice(0, actual); Span<ulong> candidates = candidateBuffer.Slice(writeIndex, actual);
Span<bool> evaluations = evaluationBuffer.Slice(0, actual); Span<bool> evaluations = evaluationBuffer.Slice(0, actual);
for (int left = 0, right = actual - 1; left < right; left++, right--)
{
ulong tmp = candidates[left];
candidates[left] = candidates[right];
candidates[right] = tmp;
}
stepper.Reset(); stepper.Reset();
evaluations[0] = stepper.InitializeCpuIsUnity(candidates[0]); evaluations[0] = stepper.InitializeCpuIsUnity(candidates[0]);
@@ -598,24 +595,28 @@ public class MersenneDivisorCycles
// return 2UL; // return 2UL;
// } // }
ulong modulus = n;
ulong nMinus1 = modulus - 1UL;
ulong nMinus2 = modulus - 2UL;
while (true) while (true)
{ {
ulong c = (NextRandomUInt64() % (n - 1UL)) + 1UL; ulong c = (NextRandomUInt64() % nMinus1) + 1UL;
ulong x = (NextRandomUInt64() % (n - 2UL)) + 2UL; ulong x = (NextRandomUInt64() % nMinus2) + 2UL;
ulong y = x; ulong y = x;
ulong d = 1UL; ulong d = 1UL;
while (d == 1UL) while (d == 1UL)
{ {
x = AdvancePolynomial(x, c, n); x = AdvancePolynomial(x, c, modulus);
y = AdvancePolynomial(y, c, n); y = AdvancePolynomial(y, c, modulus);
y = AdvancePolynomial(y, c, n); y = AdvancePolynomial(y, c, modulus);
ulong diff = x > y ? x - y : y - x; ulong diff = x > y ? x - y : y - x;
d = BinaryGcd(diff, n); d = BinaryGcd(diff, modulus);
} }
if (d != n) if (d != modulus)
{ {
return d; return d;
} }
@@ -916,6 +917,9 @@ public class MersenneDivisorCycles
return CalculateCycleLengthFallback(divisor); return CalculateCycleLengthFallback(divisor);
} }
// [ThreadStatic]
// private static HeuristicPrimeTester? _primeTester;
internal static bool TryCalculateCycleLengthHeuristic(ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength, bool skipPrimeOrderHeuristic = false) internal static bool TryCalculateCycleLengthHeuristic(ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength, bool skipPrimeOrderHeuristic = false)
{ {
if ((divisor & (divisor - 1UL)) == 0UL) if ((divisor & (divisor - 1UL)) == 0UL)
@@ -936,8 +940,10 @@ public class MersenneDivisorCycles
// Console.WriteLine($"MersenneDivisorCycles.CalculateCycleLengthFallback hits {Volatile.Read(ref _tryCalculateCycleLengthHeuristicHits)}"); // Console.WriteLine($"MersenneDivisorCycles.CalculateCycleLengthFallback hits {Volatile.Read(ref _tryCalculateCycleLengthHeuristicHits)}");
// } // }
// HeuristicPrimeTester primeTester = _primeTester ??= new();
// if (!skipPrimeOrderHeuristic && HeuristicPrimeTester.Exclusive.IsPrimeCpu(divisor, CancellationToken.None)) // if (!skipPrimeOrderHeuristic && HeuristicPrimeTester.Exclusive.IsPrimeCpu(divisor, CancellationToken.None))
if (!skipPrimeOrderHeuristic && PrimeTester.IsPrimeCpu(divisor, CancellationToken.None)) // if (!skipPrimeOrderHeuristic && primeTester.IsPrimeGpu(divisor))
if (!skipPrimeOrderHeuristic && PrimeTester.IsPrimeGpu(divisor))
{ {
ulong computedOrder = PrimeOrderCalculator.Calculate( ulong computedOrder = PrimeOrderCalculator.Calculate(
divisor, divisor,
@@ -1,5 +1,6 @@
using System.Buffers; using System.Buffers;
using System.Collections.Concurrent; using System.Threading;
using System.Threading.Tasks;
using System.Globalization; using System.Globalization;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -93,9 +94,15 @@ public static class MersenneNumberDivisorByDivisorTester
} }
else else
{ {
while (startIndex > 0 && candidates[startIndex - 1] >= startPrime) while (startIndex > 0)
{ {
startIndex--; int previousIndex = startIndex - 1;
if (candidates[previousIndex] < startPrime)
{
break;
}
startIndex = previousIndex;
} }
} }
} }
@@ -161,14 +168,10 @@ public static class MersenneNumberDivisorByDivisorTester
if (primesToTest.Count == 0) if (primesToTest.Count == 0)
{ {
if (applyStartPrime) string message = applyStartPrime
{ ? $"No primes greater than or equal to {startPrime.ToString(CultureInfo.InvariantCulture)} were found for --mersenne=bydivisor."
Console.WriteLine($"No primes greater than or equal to {startPrime.ToString(CultureInfo.InvariantCulture)} were found for --mersenne=bydivisor."); : "No prime candidates remain for --mersenne=bydivisor after filtering.";
} Console.WriteLine(message);
else
{
Console.WriteLine("No prime candidates remain for --mersenne=bydivisor after filtering.");
}
return; return;
} }
@@ -239,19 +242,20 @@ public static class MersenneNumberDivisorByDivisorTester
void ProcessPrime(ulong prime) void ProcessPrime(ulong prime)
{ {
Console.WriteLine($"Task started {prime}"); Console.WriteLine($"Processing {prime}");
bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted); bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted);
if (!isPrime) if (!isPrime)
{ {
markComposite(); markComposite();
printResult(prime, true, true, false); printResult(prime, true, true, false);
Console.WriteLine($"Finished processing {prime}");
return; return;
} }
clearComposite(); clearComposite();
printResult(prime, true, divisorsExhausted, true); printResult(prime, true, divisorsExhausted, true);
Console.WriteLine($"Task finished {prime}"); Console.WriteLine($"Finished processing {prime}");
} }
if (workerCount == 1) if (workerCount == 1)
@@ -263,12 +267,7 @@ public static class MersenneNumberDivisorByDivisorTester
} }
else else
{ {
var scheduler = UnboundedTaskScheduler.Instance; TaskScheduler scheduler = UnboundedTaskScheduler.Instance;
ParallelOptions options = new()
{
MaxDegreeOfParallelism = workerCount,
TaskScheduler = scheduler
};
int totalCount = filteredPrimes.Count; int totalCount = filteredPrimes.Count;
int partitionSize = chunkSize < 1 ? 1 : chunkSize; int partitionSize = chunkSize < 1 ? 1 : chunkSize;
@@ -277,13 +276,41 @@ public static class MersenneNumberDivisorByDivisorTester
partitionSize = totalCount; partitionSize = totalCount;
} }
Parallel.ForEach(Partitioner.Create(0, totalCount, partitionSize), options, range => int taskCount = (totalCount + partitionSize - 1) / partitionSize;
Task[] tasks = new Task[taskCount];
var startGate = new ManualResetEventSlim(initialState: false);
int taskIndex = 0;
for (int start = 0; start < totalCount; start += partitionSize)
{ {
for (int index = range.Item1; index < range.Item2; index++) int rangeStart = start;
int rangeEnd = rangeStart + partitionSize;
if (rangeEnd > totalCount)
{
rangeEnd = totalCount;
}
tasks[taskIndex++] = Task.Factory.StartNew(
() =>
{
startGate.Wait();
Console.WriteLine($"Task started for range {rangeStart}");
for (int index = rangeStart; index < rangeEnd; index++)
{ {
ProcessPrime(filteredPrimes[index]); ProcessPrime(filteredPrimes[index]);
} }
});
Console.WriteLine($"Task finished for range {rangeStart}");
},
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
scheduler);
}
startGate.Set();
Task.WaitAll(tasks);
startGate.Dispose();
} }
} }
} }
+1 -8
View File
@@ -147,13 +147,10 @@ public sealed class MersenneNumberTester(
UInt128[] qs = qsBuffer; UInt128[] qs = qsBuffer;
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
var execution = gpuLease.EnterExecutionScope();
var accelerator = gpuLease.Accelerator; var accelerator = gpuLease.Accelerator;
var stream = gpuLease.Stream; var stream = gpuLease.Stream;
var orderKernel = gpuLease.OrderKernel!; var orderKernel = gpuLease.OrderKernel!;
try
{
// Guard long-running kernels by chunking the warm-up across batches. // Guard long-running kernels by chunking the warm-up across batches.
// Reuse the same ScanBatchSize knob used for scanning. // Reuse the same ScanBatchSize knob used for scanning.
int batchSize = GpuConstants.ScanBatchSize; int batchSize = GpuConstants.ScanBatchSize;
@@ -195,14 +192,10 @@ public sealed class MersenneNumberTester(
orderBuffer.Dispose(); orderBuffer.Dispose();
qBuffer.Dispose(); qBuffer.Dispose();
} }
}
finally
{
execution.Dispose();
gpuLease.Dispose(); gpuLease.Dispose();
ArrayPool<UInt128>.Shared.Return(qs); ArrayPool<UInt128>.Shared.Return(qs);
} }
}
public bool IsMersennePrime(ulong exponent) public bool IsMersennePrime(ulong exponent)
{ {
@@ -14,7 +14,7 @@ public static class MersennePrimeFactorTester
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsPrimeFactor(ulong p, ulong q, CancellationToken ct) public static bool IsPrimeFactor(ulong p, ulong q, CancellationToken ct)
{ {
if (q < 2UL || (q & 1UL) == 0) if (q < 2UL)
{ {
return false; return false;
} }
@@ -34,7 +34,7 @@ public static class MersennePrimeFactorTester
// Atomic.Add(ref _isPrimeFactorHits, 1UL); // Atomic.Add(ref _isPrimeFactorHits, 1UL);
// Console.WriteLine($"MersennePrimeFactorTester.IsPrimeFactor hits {Volatile.Read(ref _isPrimeFactorHits)}"); // Console.WriteLine($"MersennePrimeFactorTester.IsPrimeFactor hits {Volatile.Read(ref _isPrimeFactorHits)}");
if (!PrimeTester.IsPrimeCpu(q, ct)) if (!PrimeTester.IsPrime(q))
{ {
return false; return false;
} }
@@ -247,7 +247,7 @@ public static class MersennePrimeFactorTester
// Console.WriteLine($"MersennePrimeFactorTester.Factor64 hits {Volatile.Read(ref _factor64Hits)}"); // Console.WriteLine($"MersennePrimeFactorTester.Factor64 hits {Volatile.Read(ref _factor64Hits)}");
// if (HeuristicPrimeTester.Exclusive.IsPrime(m, ct)) // if (HeuristicPrimeTester.Exclusive.IsPrime(m, ct))
if (PrimeTester.IsPrimeCpu(m, ct)) if (PrimeTester.IsPrime(m))
{ {
dict[m] = 1; dict[m] = 1;
continue; continue;
+1 -1
View File
@@ -34,7 +34,7 @@ public readonly struct MontgomeryDivisorData(ulong modulus, ulong nPrime, ulong
ulong nPrime = ComputeMontgomeryNPrime(modulus); ulong nPrime = ComputeMontgomeryNPrime(modulus);
ulong montgomeryOne = ComputeMontgomeryResidue(UInt128Numbers.OneShiftedLeft64, modulus); ulong montgomeryOne = ComputeMontgomeryResidue(UInt128Numbers.OneShiftedLeft64, modulus);
ulong montgomeryTwo = ComputeMontgomeryResidue(UInt128Numbers.OneShiftedLeft64x2, modulus); ulong montgomeryTwo = ComputeMontgomeryResidue(UInt128Numbers.OneShiftedLeft64x2, modulus);
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiply(montgomeryTwo, montgomeryTwo, modulus, nPrime); ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiplyCpu(montgomeryTwo, montgomeryTwo, modulus, nPrime);
return new MontgomeryDivisorData( return new MontgomeryDivisorData(
modulus, modulus,
+164 -237
View File
@@ -1,4 +1,3 @@
using System;
using System.Buffers; using System.Buffers;
using System.Diagnostics; using System.Diagnostics;
using System.Numerics; using System.Numerics;
@@ -108,7 +107,7 @@ internal static partial class PrimeOrderCalculator
return candidateOrder; return candidateOrder;
} }
// private static ulong specialMaxHits;
private static bool TrySpecialMaxCpu(ulong phi, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData) private static bool TrySpecialMaxCpu(ulong phi, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
{ {
int length = factors.Count; int length = factors.Count;
@@ -121,16 +120,16 @@ internal static partial class PrimeOrderCalculator
ReadOnlySpan<FactorEntry> factorSpan = new(factors.Factors, 0, length); ReadOnlySpan<FactorEntry> factorSpan = new(factors.Factors, 0, length);
if (length <= 32)
{
Span<ulong> stackBuffer = stackalloc ulong[length]; Span<ulong> stackBuffer = stackalloc ulong[length];
if (length <= 7)
{
return EvaluateSpecialMaxCandidates(stackBuffer, factorSpan, phi, prime, divisorData); return EvaluateSpecialMaxCandidates(stackBuffer, factorSpan, phi, prime, divisorData);
} }
ulong[] rented = ThreadStaticPools.UlongPool.Rent(length); // Atomic.Add(ref specialMaxHits, 1);
bool result = EvaluateSpecialMaxCandidates(rented.AsSpan(0, length), factorSpan, phi, prime, divisorData); // Console.WriteLine($"Special max GPU hit {specialMaxHits} ({length})");
ThreadStaticPools.UlongPool.Return(rented, clearArray: false); return EvaluateSpecialMaxCandidatesGpu(stackBuffer, factorSpan, phi, prime, divisorData);
return result;
} }
private static bool EvaluateSpecialMaxCandidates(Span<ulong> buffer, ReadOnlySpan<FactorEntry> factors, ulong phi, ulong prime, in MontgomeryDivisorData divisorData) private static bool EvaluateSpecialMaxCandidates(Span<ulong> buffer, ReadOnlySpan<FactorEntry> factors, ulong phi, ulong prime, in MontgomeryDivisorData divisorData)
@@ -299,22 +298,16 @@ internal static partial class PrimeOrderCalculator
Span<ulong> candidates = candidateBuffer[..actual]; Span<ulong> candidates = candidateBuffer[..actual];
Span<bool> evaluations = evaluationBuffer[..actual]; Span<bool> evaluations = evaluationBuffer[..actual];
for (int left = 0, right = actual - 1; left < right; left++, right--)
{
ulong tmp = candidates[left];
candidates[left] = candidates[right];
candidates[right] = tmp;
}
stepper.Reset(); stepper.Reset();
evaluations[0] = stepper.InitializeCpuIsUnity(candidates[0]); int last = actual - 1;
for (int j = 1; j < actual; j++) evaluations[last] = stepper.InitializeCpuIsUnity(candidates[last]);
for (int j = last - 1; j >= 0; j--)
{ {
evaluations[j] = stepper.ComputeNextIsUnity(candidates[j]); evaluations[j] = stepper.ComputeNextIsUnity(candidates[j]);
} }
for (int j = actual - 1; j >= 0; j--) for (int j = 0; j < actual; j++)
{ {
if (!evaluations[j]) if (!evaluations[j])
{ {
@@ -464,16 +457,16 @@ internal static partial class PrimeOrderCalculator
} }
Span<ulong> candidates = buffer[..actual]; Span<ulong> candidates = buffer[..actual];
candidates.Reverse();
stepper.Reset(); stepper.Reset();
if (stepper.InitializeCpuIsUnity(candidates[0])) int last = actual - 1;
if (stepper.InitializeCpuIsUnity(candidates[last]))
{ {
return true; return true;
} }
for (int j = 1; j < actual; j++) for (int j = last - 1; j >= 0; j--)
{ {
if (stepper.ComputeNextIsUnity(candidates[j])) if (stepper.ComputeNextIsUnity(candidates[j]))
{ {
@@ -694,13 +687,16 @@ internal static partial class PrimeOrderCalculator
private static void SortCandidates(ulong prime, ulong? previousOrder, List<ulong> candidates) private static void SortCandidates(ulong prime, ulong? previousOrder, List<ulong> candidates)
{ {
ulong previous = previousOrder ?? 0UL; ulong previous = previousOrder ?? 0UL;
int previousGroup = previousOrder.HasValue ? GetGroup(previousOrder.Value, prime) : 1;
bool hasPrevious = previousOrder.HasValue; bool hasPrevious = previousOrder.HasValue;
ulong threshold1 = prime >> 3;
ulong threshold2 = prime >> 2;
ulong threshold3 = (prime * 3UL) >> 3;
int previousGroup = hasPrevious ? GetGroup(previous, threshold1, threshold2, threshold3) : 1;
candidates.Sort((x, y) => candidates.Sort((x, y) =>
{ {
CandidateKey keyX = BuildKey(x, prime, previous, previousGroup, hasPrevious); CandidateKey keyX = BuildKey(x, previous, previousGroup, hasPrevious, threshold1, threshold2, threshold3);
CandidateKey keyY = BuildKey(y, prime, previous, previousGroup, hasPrevious); CandidateKey keyY = BuildKey(y, previous, previousGroup, hasPrevious, threshold1, threshold2, threshold3);
int compare = keyX.Primary.CompareTo(keyY.Primary); int compare = keyX.Primary.CompareTo(keyY.Primary);
if (compare != 0) if (compare != 0)
{ {
@@ -717,9 +713,9 @@ internal static partial class PrimeOrderCalculator
}); });
} }
private static CandidateKey BuildKey(ulong value, ulong prime, ulong previous, int previousGroup, bool hasPrevious) private static CandidateKey BuildKey(ulong value, ulong previous, int previousGroup, bool hasPrevious, ulong threshold1, ulong threshold2, ulong threshold3)
{ {
int group = GetGroup(value, prime); int group = GetGroup(value, threshold1, threshold2, threshold3);
if (group == 0) if (group == 0)
{ {
return new CandidateKey(int.MaxValue, long.MaxValue, long.MaxValue); return new CandidateKey(int.MaxValue, long.MaxValue, long.MaxValue);
@@ -769,21 +765,18 @@ internal static partial class PrimeOrderCalculator
return groupOffset + (isGe ? 0 : 1); return groupOffset + (isGe ? 0 : 1);
} }
private static int GetGroup(ulong value, ulong prime) private static int GetGroup(ulong value, ulong threshold1, ulong threshold2, ulong threshold3)
{ {
ulong threshold1 = prime >> 3;
if (value <= threshold1) if (value <= threshold1)
{ {
return 1; return 1;
} }
ulong threshold2 = prime >> 2;
if (value <= threshold2) if (value <= threshold2)
{ {
return 2; return 2;
} }
ulong threshold3 = (prime * 3UL) >> 3;
if (value <= threshold3) if (value <= threshold3)
{ {
return 3; return 3;
@@ -828,9 +821,11 @@ internal static partial class PrimeOrderCalculator
} }
FactorEntry factor = factors[index]; FactorEntry factor = factors[index];
int factorExponent = factor.Exponent;
ulong primeFactor = factor.Value; ulong primeFactor = factor.Value;
ulong contribution = 1UL; ulong contribution = 1UL;
for (int exponent = 0; exponent <= factor.Exponent; exponent++) ulong contributionLimit = factorExponent == 0 ? 0UL : order / primeFactor;
for (int exponent = 0; exponent <= factorExponent; exponent++)
{ {
ulong nextDivisor = divisorProduct * contribution; ulong nextDivisor = divisorProduct * contribution;
if (nextDivisor > order) if (nextDivisor > order)
@@ -844,12 +839,12 @@ internal static partial class PrimeOrderCalculator
return; return;
} }
if (exponent == factor.Exponent) if (exponent == factorExponent)
{ {
break; break;
} }
if (contribution > order / primeFactor) if (contribution > contributionLimit)
{ {
break; break;
} }
@@ -973,24 +968,45 @@ internal static partial class PrimeOrderCalculator
} }
Span<ulong> candidates = buffer[..actual]; Span<ulong> candidates = buffer[..actual];
candidates.Reverse();
stepper.Reset(); stepper.Reset();
int last = actual - 1;
if (powUsed >= powBudget && powBudget > 0) bool enforceBudget = powBudget > 0;
if (!enforceBudget)
{
powUsed++;
if (stepper.InitializeCpuIsUnity(candidates[last]))
{
return true;
}
for (int j = last - 1; j >= 0; j--)
{
powUsed++;
if (stepper.ComputeNextIsUnity(candidates[j]))
{
return true;
}
}
return false;
}
if (powUsed >= powBudget)
{ {
return true; return true;
} }
powUsed++; powUsed++;
if (stepper.InitializeCpuIsUnity(candidates[0])) if (stepper.InitializeCpuIsUnity(candidates[last]))
{ {
return true; return true;
} }
for (int j = 1; j < actual; j++) for (int j = last - 1; j >= 0; j--)
{ {
if (powUsed >= powBudget && powBudget > 0) if (powUsed >= powBudget)
{ {
return true; return true;
} }
@@ -1040,13 +1056,13 @@ internal static partial class PrimeOrderCalculator
// Span<int> exponentSlots = exponentSlotsArray.AsSpan(0, FactorSlotCount); // Span<int> exponentSlots = exponentSlotsArray.AsSpan(0, FactorSlotCount);
Span<ulong> primeSlots = stackalloc ulong[FactorSlotCount]; Span<ulong> primeSlots = stackalloc ulong[FactorSlotCount];
Span<int> exponentSlots = stackalloc int[FactorSlotCount]; Span<int> exponentSlots = stackalloc int[FactorSlotCount];
ulong[]? primeSlotArray = null;
int[]? exponentSlotArray = null;
// We don't need to worry about leftovers, because we always use indexes within the calculated counts // We don't need to worry about leftovers, because we always use indexes within the calculated counts
// primeSlots.Clear(); // primeSlots.Clear();
// exponentSlots.Clear(); // exponentSlots.Clear();
int factorCount = 0; int factorCount = 0;
Dictionary<ulong, int>? counts = null;
bool useDictionary = false;
uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit; uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit;
ulong remaining = value; ulong remaining = value;
@@ -1061,45 +1077,20 @@ internal static partial class PrimeOrderCalculator
Stack<ulong>? compositeStack = null; Stack<ulong>? compositeStack = null;
PartialFactorResult result; PartialFactorResult result;
PrimeTester primeTester = PrimeTester.Exclusive; PrimeTester primeTester = PrimeTester.Exclusive;
CancellationToken ct = CancellationToken.None;
// HeuristicPrimeTester? tester = _tester ??= new();
if (!gpuFactored) if (!gpuFactored)
{ {
// We don't need to worry about leftovers, because we always use indexes within the calculated counts PopulateSmallPrimeFactorsCpu(
// primeSlots.Clear(); value,
// exponentSlots.Clear(); limit,
ref primeSlots,
counts = ThreadStaticPools.RentUlongIntDictionary(Math.Max(FactorSlotCount, 8)); ref exponentSlots,
// counts.Clear(); ref primeSlotArray,
ref exponentSlotArray,
// bool gpuPopulated = TryPopulateSmallPrimeFactorsGpu(value, limit, primeSlots, exponentSlots, out factorCount, out remaining); out factorCount,
// if (!gpuPopulated) out remaining);
// {
// throw new InvalidOperationException($"GPU didn't populate factors for {value}");
// counts.Clear();
remaining = PopulateSmallPrimeFactorsCpu(value, limit, counts);
// }
int dictionaryCount = counts.Count;
if (dictionaryCount <= FactorSlotCount)
{
int copyIndex = 0;
foreach (KeyValuePair<ulong, int> entry in counts)
{
primeSlots[copyIndex] = entry.Key;
exponentSlots[copyIndex] = entry.Value;
copyIndex++;
}
factorCount = copyIndex;
counts.Clear();
ThreadStaticPools.ReturnUlongIntDictionary(counts);
counts = null;
}
else
{
useDictionary = true;
factorCount = dictionaryCount;
}
} }
@@ -1133,12 +1124,13 @@ internal static partial class PrimeOrderCalculator
// Atomic.Add(ref _partialFactorHits, 1UL); // Atomic.Add(ref _partialFactorHits, 1UL);
// Console.WriteLine($"Partial factor hits {Volatile.Read(ref _partialFactorHits)}"); // Console.WriteLine($"Partial factor hits {Volatile.Read(ref _partialFactorHits)}");
bool isPrime = PrimeTester.IsPrimeGpu(composite); // bool isPrime = PrimeTester.IsPrimeCpu(composite, CancellationToken.None);
bool isPrime = PrimeTester.IsPrime(composite);
// bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite); // bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite);
if (isPrime) if (isPrime)
{ {
AddFactorToCollector(ref useDictionary, ref counts, primeSlots, exponentSlots, ref factorCount, composite, 1); AddFactorToCollector(ref primeSlots, ref exponentSlots, ref primeSlotArray, ref exponentSlotArray, ref factorCount, composite, 1);
continue; continue;
} }
@@ -1200,6 +1192,7 @@ internal static partial class PrimeOrderCalculator
// Atomic.Add(ref _partialFactorPendingHits, 1UL); // Atomic.Add(ref _partialFactorPendingHits, 1UL);
// Console.WriteLine($"Partial factor pending hits {Volatile.Read(ref _partialFactorPendingHits)}"); // Console.WriteLine($"Partial factor pending hits {Volatile.Read(ref _partialFactorPendingHits)}");
// bool isPrime = PrimeTester.IsPrimeCpu(composite, CancellationToken.None);
bool isPrime = PrimeTester.IsPrimeGpu(composite); bool isPrime = PrimeTester.IsPrimeGpu(composite);
// bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite); // bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite);
@@ -1209,7 +1202,7 @@ internal static partial class PrimeOrderCalculator
if (entry.IsPrime) if (entry.IsPrime)
{ {
AddFactorToCollector(ref useDictionary, ref counts, primeSlots, exponentSlots, ref factorCount, composite, 1); AddFactorToCollector(ref primeSlots, ref exponentSlots, ref primeSlotArray, ref exponentSlotArray, ref factorCount, composite, 1);
} }
// composite is never smaller on the execution path // composite is never smaller on the execution path
// else if (composite > 1UL) // else if (composite > 1UL)
@@ -1237,42 +1230,25 @@ internal static partial class PrimeOrderCalculator
// Atomic.Add(ref _partialFactorCofactorHits, 1UL); // Atomic.Add(ref _partialFactorCofactorHits, 1UL);
// Console.WriteLine($"Partial factor cofactor hits {Volatile.Read(ref _partialFactorCofactorHits)}"); // Console.WriteLine($"Partial factor cofactor hits {Volatile.Read(ref _partialFactorCofactorHits)}");
cofactorIsPrime = PrimeTester.IsPrimeCpu(cofactor, CancellationToken.None); cofactorIsPrime = PrimeTester.IsPrimeGpu(cofactor);
// cofactorIsPrime = PrimeTester.IsPrimeGpu(cofactor);
// cofactorIsPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(cofactor); // cofactorIsPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(cofactor);
} }
ArrayPool<FactorEntry> pool = ThreadStaticPools.FactorEntryPool; ArrayPool<FactorEntry> pool = ThreadStaticPools.FactorEntryPool;
bool temp; bool temp;
if (useDictionary) if (factorCount == 0)
{ {
temp = counts is null || counts.Count == 0; if (cofactor == value)
if (temp && cofactor == value)
{ {
result = PartialFactorResult.Rent(null, cofactor, false, 0, cofactorIsPrime); result = PartialFactorResult.Rent(null, cofactor, false, 0, cofactorIsPrime);
goto ReturnResult; goto ReturnResult;
} }
if (temp)
{
result = PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0, cofactorIsPrime); result = PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0, cofactorIsPrime);
goto ReturnResult; goto ReturnResult;
} }
FactorEntry[] factors = pool.Rent(counts!.Count);
index = 0;
foreach (KeyValuePair<ulong, int> entry in counts)
{
factors[index] = new FactorEntry(entry.Key, entry.Value, true);
index++;
}
Array.Sort(factors, static (a, b) => a.Value.CompareTo(b.Value));
temp = cofactor == 1UL;
result = PartialFactorResult.Rent(factors, cofactor, temp, index, cofactorIsPrime);
goto ReturnResult;
}
// This will never happen in production code. We'll always get at least 1 factor // This will never happen in production code. We'll always get at least 1 factor
// if (factorCount == 0) // if (factorCount == 0)
// { // {
@@ -1309,14 +1285,10 @@ internal static partial class PrimeOrderCalculator
result = PartialFactorResult.Rent(array, cofactor, temp, arrayIndex, cofactorIsPrime); result = PartialFactorResult.Rent(array, cofactor, temp, arrayIndex, cofactorIsPrime);
ReturnResult: ReturnResult:
// ThreadStaticPools.UlongPool.Return(primeSlotsArray); if (primeSlotArray is not null)
// ThreadStaticPools.IntPool.Return(exponentSlotsArray);
if (counts is not null)
{ {
counts.Clear(); ThreadStaticPools.UlongPool.Return(primeSlotArray, clearArray: false);
ThreadStaticPools.ReturnUlongIntDictionary(counts); ThreadStaticPools.IntPool.Return(exponentSlotArray!, clearArray: false);
} }
if (compositeStack is not null) if (compositeStack is not null)
@@ -1330,35 +1302,28 @@ internal static partial class PrimeOrderCalculator
return result; return result;
} }
private static void PopulateSmallPrimeFactorsCpu(
private static bool TryPopulateSmallPrimeFactorsCpu(
ulong value, ulong value,
uint limit, uint limit,
Span<ulong> primeTargets, ref Span<ulong> primeTargets,
Span<int> exponentTargets, ref Span<int> exponentTargets,
ref ulong[]? primeArray,
ref int[]? exponentArray,
out int factorCount, out int factorCount,
out ulong remaining) out ulong remaining)
{ {
factorCount = 0; factorCount = 0;
remaining = value; ulong remainingLocal = value;
// value will never <= 1 in production code if (value <= 1UL)
// if (value <= 1UL) {
// { remaining = remainingLocal;
// return true; return;
// } }
int capacity = Math.Min(primeTargets.Length, exponentTargets.Length);
// capacity will never equal 0 in production code
// if (capacity == 0)
// {
// return false;
// }
uint[] primes = PrimesGenerator.SmallPrimes; uint[] primes = PrimesGenerator.SmallPrimes;
ulong[] squares = PrimesGenerator.SmallPrimesPow2; ulong[] squares = PrimesGenerator.SmallPrimesPow2;
int primeCount = primes.Length; int primeCount = primes.Length;
ulong remainingLocal = value;
uint effectiveLimit = limit == 0 ? uint.MaxValue : limit; uint effectiveLimit = limit == 0 ? uint.MaxValue : limit;
for (int i = 0; i < primeCount && remainingLocal > 1UL; i++) for (int i = 0; i < primeCount && remainingLocal > 1UL; i++)
@@ -1377,13 +1342,13 @@ internal static partial class PrimeOrderCalculator
break; break;
} }
ulong primeValue = primeCandidate;
// primeCandidate will never equal 0 in production code // primeCandidate will never equal 0 in production code
// if (primeCandidate == 0UL) // if (primeCandidate == 0UL)
// { // {
// continue; // continue;
// } // }
ulong primeValue = primeCandidate;
if ((remainingLocal % primeValue) != 0UL) if ((remainingLocal % primeValue) != 0UL)
{ {
continue; continue;
@@ -1395,10 +1360,13 @@ internal static partial class PrimeOrderCalculator
continue; continue;
} }
if (factorCount >= capacity) EnsureCollectorCapacity(
{ ref primeTargets,
throw new InvalidOperationException($"Capacity is smaller than factor count"); ref exponentTargets,
} ref primeArray,
ref exponentArray,
factorCount,
factorCount + 1);
primeTargets[factorCount] = primeValue; primeTargets[factorCount] = primeValue;
exponentTargets[factorCount] = exponent; exponentTargets[factorCount] = exponent;
@@ -1406,7 +1374,6 @@ internal static partial class PrimeOrderCalculator
} }
remaining = remainingLocal; remaining = remainingLocal;
return true;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1474,46 +1441,6 @@ internal static partial class PrimeOrderCalculator
return exponent; return exponent;
} }
private static ulong PopulateSmallPrimeFactorsCpu(ulong value, uint limit, Dictionary<ulong, int> counts)
{
ulong remaining = value;
uint[] primes = PrimesGenerator.SmallPrimes;
ulong[] squares = PrimesGenerator.SmallPrimesPow2;
int primeCount = primes.Length;
for (int i = 0; i < primeCount; i++)
{
uint primeCandidate = primes[i];
if (primeCandidate > limit)
{
break;
}
if (squares[i] > remaining)
{
break;
}
if ((remaining % primeCandidate) != 0UL)
{
continue;
}
int exponent = 0;
do
{
// TODO: Implement DivRem like solution or residue stepper to re-use previous iteration results in new calculation. Add / use method, likely to ULongExtensions
remaining /= primeCandidate;
exponent++;
}
while ((remaining % primeCandidate) == 0UL);
counts[primeCandidate] = exponent;
}
return remaining;
}
private static long CreateDeadlineTimestamp(int milliseconds) private static long CreateDeadlineTimestamp(int milliseconds)
{ {
if (milliseconds <= 0) if (milliseconds <= 0)
@@ -1636,38 +1563,11 @@ internal static partial class PrimeOrderCalculator
} }
} }
private static bool TryAppendFactor(Span<ulong> primes, Span<int> exponents, ref int count, ulong prime, int exponent)
{
if (exponent <= 0)
{
return true;
}
for (int i = 0; i < count; i++)
{
if (primes[i] == prime)
{
exponents[i] += exponent;
return true;
}
}
if (count >= primes.Length)
{
return false;
}
primes[count] = prime;
exponents[count] = exponent;
count++;
return true;
}
private static void AddFactorToCollector( private static void AddFactorToCollector(
ref bool useDictionary, ref Span<ulong> primes,
ref Dictionary<ulong, int>? counts, ref Span<int> exponents,
Span<ulong> primes, ref ulong[]? primeArray,
Span<int> exponents, ref int[]? exponentArray,
ref int count, ref int count,
ulong prime, ulong prime,
int exponent) int exponent)
@@ -1677,41 +1577,65 @@ internal static partial class PrimeOrderCalculator
return; return;
} }
if (useDictionary)
{
AddFactor(counts!, prime, exponent);
return;
}
if (TryAppendFactor(primes, exponents, ref count, prime, exponent))
{
return;
}
counts ??= ThreadStaticPools.RentUlongIntDictionary(Math.Max(count, 8));
CopyFactorsToDictionary(primes, exponents, count, counts);
count = 0;
useDictionary = true;
AddFactor(counts, prime, exponent);
}
private static void CopyFactorsToDictionary(
in ReadOnlySpan<ulong> primes,
in ReadOnlySpan<int> exponents,
int count,
Dictionary<ulong, int> target)
{
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
ulong prime = primes[i]; if (primes[i] == prime)
int exponent = exponents[i];
if (prime == 0UL || exponent == 0)
{ {
continue; exponents[i] += exponent;
return;
}
} }
target[prime] = exponent; EnsureCollectorCapacity(
ref primes,
ref exponents,
ref primeArray,
ref exponentArray,
count,
count + 1);
primes[count] = prime;
exponents[count] = exponent;
count++;
} }
private static void EnsureCollectorCapacity(
ref Span<ulong> primes,
ref Span<int> exponents,
ref ulong[]? primeArray,
ref int[]? exponentArray,
int count,
int requiredCapacity)
{
if (requiredCapacity <= primes.Length)
{
return;
}
int newCapacity = primes.Length << 1;
if (newCapacity < requiredCapacity)
{
newCapacity = requiredCapacity;
}
ulong[] newPrimeArray = ThreadStaticPools.UlongPool.Rent(newCapacity);
int[] newExponentArray = ThreadStaticPools.IntPool.Rent(newCapacity);
Span<ulong> newPrimeSpan = newPrimeArray.AsSpan();
Span<int> newExponentSpan = newExponentArray.AsSpan();
primes.Slice(0, count).CopyTo(newPrimeSpan);
exponents.Slice(0, count).CopyTo(newExponentSpan);
if (primeArray is not null)
{
ThreadStaticPools.UlongPool.Return(primeArray, clearArray: false);
ThreadStaticPools.IntPool.Return(exponentArray!, clearArray: false);
}
primeArray = newPrimeArray;
exponentArray = newExponentArray;
primes = newPrimeSpan;
exponents = newExponentSpan;
} }
private static ulong CalculateByFactorizationCpu(ulong prime, in MontgomeryDivisorData divisorData) private static ulong CalculateByFactorizationCpu(ulong prime, in MontgomeryDivisorData divisorData)
@@ -1761,6 +1685,8 @@ internal static partial class PrimeOrderCalculator
// private static ulong _factorCompletelyHits; // private static ulong _factorCompletelyHits;
// private static ulong _factorCompletelyPollardRhoHits; // private static ulong _factorCompletelyPollardRhoHits;
// [ThreadStatic]
// private static HeuristicPrimeTester? _tester;
private static void FactorCompletely(ulong value, Dictionary<ulong, int> counts, bool knownComposite) private static void FactorCompletely(ulong value, Dictionary<ulong, int> counts, bool knownComposite)
{ {
@@ -1771,11 +1697,11 @@ internal static partial class PrimeOrderCalculator
// Atomic.Add(ref _factorCompletelyHits, 1UL); // Atomic.Add(ref _factorCompletelyHits, 1UL);
// Console.WriteLine($"Factor completely hits {Volatile.Read(ref _factorCompletelyHits)}"); // Console.WriteLine($"Factor completely hits {Volatile.Read(ref _factorCompletelyHits)}");
// PrimeTester primeTester = PrimeTester.Exclusive; // HeuristicPrimeTester tester = _tester ??= new();
// bool isPrime = primeTester.HeuristicIsPrimeGpu(value); // bool isPrime = primeTester.HeuristicIsPrimeGpu(value);
// bool isPrime = PrimeTester.IsPrimeCpu(value, CancellationToken.None); bool isPrime = PrimeTester.IsPrimeGpu(value);
bool isPrime = PrimeTester.IsPrimeCpu(value, CancellationToken.None); // bool isPrime = PrimeTester.IsPrimeGpu(value);
// bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(value); // bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(value);
if (!knownComposite && isPrime) if (!knownComposite && isPrime)
{ {
@@ -1790,8 +1716,8 @@ internal static partial class PrimeOrderCalculator
// Console.WriteLine($"Factor completely after PollardRho hits {Volatile.Read(ref _factorCompletelyPollardRhoHits)}"); // Console.WriteLine($"Factor completely after PollardRho hits {Volatile.Read(ref _factorCompletelyPollardRhoHits)}");
// isPrime = primeTester.HeuristicIsPrimeGpu(factor); // isPrime = primeTester.HeuristicIsPrimeGpu(factor);
// isPrime = PrimeTester.IsPrimeCpu(factor, CancellationToken.None); isPrime = PrimeTester.IsPrimeGpu(factor);
isPrime = PrimeTester.IsPrimeCpu(factor, CancellationToken.None); // isPrime = PrimeTester.IsPrimeGpu(factor);
// isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factor); // isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factor);
if (isPrime) if (isPrime)
{ {
@@ -1827,7 +1753,8 @@ internal static partial class PrimeOrderCalculator
// Atomic.Add(ref _partialFactorCofactorHits, 1UL); // Atomic.Add(ref _partialFactorCofactorHits, 1UL);
// Console.WriteLine($"Partial factor cofactor hits {Volatile.Read(ref _partialFactorCofactorHits)}"); // Console.WriteLine($"Partial factor cofactor hits {Volatile.Read(ref _partialFactorCofactorHits)}");
isPrime = PrimeTester.IsPrimeCpu(quotient, CancellationToken.None); isPrime = PrimeTester.IsPrimeGpu(quotient);
// isPrime = PrimeTester.IsPrimeGpu(quotient);
// isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(quotient); // isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(quotient);
} }
+50 -25
View File
@@ -19,22 +19,14 @@ internal static partial class PrimeOrderCalculator
var exponentBufferArray = ThreadStaticPools.IntPool.Rent(GpuSmallPrimeFactorSlots); var exponentBufferArray = ThreadStaticPools.IntPool.Rent(GpuSmallPrimeFactorSlots);
Span<ulong> primeBuffer = primeBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots); Span<ulong> primeBuffer = primeBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots);
Span<int> exponentBuffer = exponentBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots); Span<int> exponentBuffer = exponentBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots);
// primeBuffer.Clear();
// exponentBuffer.Clear();
remaining = value; remaining = value;
try
{
var lease = GpuKernelPool.GetKernel(useGpuOrder: true); var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
var execution = lease.EnterExecutionScope();
try
{
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = lease.Accelerator;
AcceleratorStream stream = lease.Stream; AcceleratorStream stream = lease.Stream;
SmallPrimeFactorTables tables = GpuKernelPool.EnsureSmallPrimeFactorTables(accelerator); SmallPrimeFactorTables tables = GpuKernelPool.EnsureSmallPrimeFactorTables(lease.Kernels, accelerator);
SmallPrimeFactorScratch scratch = GpuKernelPool.EnsureSmallPrimeFactorScratch(accelerator, GpuSmallPrimeFactorSlots); SmallPrimeFactorScratch scratch = GpuKernelPool.EnsureSmallPrimeFactorScratch(lease.Kernels, accelerator, GpuSmallPrimeFactorSlots);
// scratch.Clear();
var kernel = lease.SmallPrimeFactorKernel; var kernel = lease.SmallPrimeFactorKernel;
kernel( kernel(
@@ -68,27 +60,60 @@ internal static partial class PrimeOrderCalculator
{ {
ulong primeValue = primeBuffer[i]; ulong primeValue = primeBuffer[i];
int exponent = exponentBuffer[i]; int exponent = exponentBuffer[i];
// This will never happen in production code
// if (primeValue == 0UL || exponent == 0)
// {
// continue;
// }
counts.Add(primeValue, exponent); counts.Add(primeValue, exponent);
} }
return true;
}
finally
{
execution.Dispose();
lease.Dispose(); lease.Dispose();
}
}
finally
{
ThreadStaticPools.UlongPool.Return(primeBufferArray); ThreadStaticPools.UlongPool.Return(primeBufferArray);
ThreadStaticPools.IntPool.Return(exponentBufferArray); ThreadStaticPools.IntPool.Return(exponentBufferArray);
return true;
} }
private static bool EvaluateSpecialMaxCandidatesGpu(
Span<ulong> buffer,
ReadOnlySpan<FactorEntry> factors,
ulong phi,
ulong prime,
in MontgomeryDivisorData divisorData)
{
if (factors.Length == 0)
{
return true;
}
int factorCount = factors.Length;
Span<ulong> factorSpan = buffer[..factorCount];
for (int i = 0; i < factorCount; i++)
{
factorSpan[i] = factors[i].Value;
}
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
Accelerator accelerator = lease.Accelerator;
AcceleratorStream stream = lease.Stream;
SpecialMaxScratch scratch = GpuKernelPool.EnsureSpecialMaxScratch(lease.Kernels, accelerator, factorCapacity: factorCount);
scratch.FactorsView.SubView(0, factorCount).CopyFromCPU(ref MemoryMarshal.GetReference(factorSpan), factorCount);
var kernel = lease.SpecialMaxKernel;
kernel(
stream,
1,
phi,
scratch.FactorsView,
factorCount,
divisorData,
scratch.CandidatesView,
scratch.ResultView);
stream.Synchronize();
ushort result = 0;
scratch.ResultView.CopyToCPU(ref result, 1);
lease.Dispose();
return result != 0;
} }
} }
@@ -193,9 +193,7 @@ internal static partial class PrimeOrderCalculator
// { // {
// return order; // return order;
// } // }
FactorEntry128[]? tempArray = pool.Rent(length + 1); FactorEntry128[] tempArray = pool.Rent(length + 1);
try
{
Span<FactorEntry128> buffer = tempArray.AsSpan(0, length); Span<FactorEntry128> buffer = tempArray.AsSpan(0, length);
factorSpan.CopyTo(buffer); factorSpan.CopyTo(buffer);
@@ -228,12 +226,8 @@ internal static partial class PrimeOrderCalculator
} }
} }
return order;
}
finally
{
pool.Return(tempArray, clearArray: false); pool.Return(tempArray, clearArray: false);
} return order;
} }
private static bool TryConfirmOrderWide(in UInt128 prime, in UInt128 order, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config) private static bool TryConfirmOrderWide(in UInt128 prime, in UInt128 order, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
@@ -956,7 +950,7 @@ internal static partial class PrimeOrderCalculator
if (value <= ulong.MaxValue) if (value <= ulong.MaxValue)
{ {
// return HeuristicPrimeTester.Exclusive.IsPrimeCpu((ulong)value, CancellationToken.None); // return HeuristicPrimeTester.Exclusive.IsPrimeCpu((ulong)value, CancellationToken.None);
return PrimeTester.IsPrimeCpu((ulong)value, CancellationToken.None); return PrimeTester.IsPrime((ulong)value);
} }
if ((value & UInt128.One) == UInt128.Zero) if ((value & UInt128.One) == UInt128.Zero)
+543 -178
View File
@@ -1,11 +1,11 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Runtime.InteropServices;
using ILGPU; using ILGPU;
using ILGPU.Algorithms;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
@@ -22,7 +22,7 @@ public sealed class PrimeTester
public static PrimeTester Exclusive => _tester ??= new(); public static PrimeTester Exclusive => _tester ??= new();
public bool IsPrime(ulong n, CancellationToken ct) public static bool IsPrime(ulong n)
{ {
if (n <= 1UL) if (n <= 1UL)
{ {
@@ -48,9 +48,31 @@ public sealed class PrimeTester
if (result) if (result)
{ {
var smallPrimeDivisorsLength = PrimesGenerator.SmallPrimes.Length;
uint[] smallPrimeDivisors = PrimesGenerator.SmallPrimes; uint[] smallPrimeDivisors = PrimesGenerator.SmallPrimes;
ulong[] smallPrimeDivisorsMul = PrimesGenerator.SmallPrimesPow2; ulong[] smallPrimeDivisorsMul = PrimesGenerator.SmallPrimesPow2;
ulong nMod10 = n.Mod10();
switch (nMod10)
{
case 1UL:
smallPrimeDivisors = PrimesGenerator.SmallPrimesLastOne;
smallPrimeDivisorsMul = PrimesGenerator.SmallPrimesPow2LastOne;
break;
case 3UL:
smallPrimeDivisors = DivisorGenerator.SmallPrimesLastThree;
smallPrimeDivisorsMul = DivisorGenerator.SmallPrimesPow2LastThree;
break;
case 7UL:
smallPrimeDivisors = PrimesGenerator.SmallPrimesLastSeven;
smallPrimeDivisorsMul = PrimesGenerator.SmallPrimesPow2LastSeven;
break;
case 9UL:
smallPrimeDivisors = DivisorGenerator.SmallPrimesLastNine;
smallPrimeDivisorsMul = DivisorGenerator.SmallPrimesPow2LastNine;
break;
}
int smallPrimeDivisorsLength = smallPrimeDivisors.Length;
for (int i = 0; i < smallPrimeDivisorsLength; i++) for (int i = 0; i < smallPrimeDivisorsLength; i++)
{ {
if (smallPrimeDivisorsMul[i] > n) if (smallPrimeDivisorsMul[i] > n)
@@ -70,58 +92,85 @@ public sealed class PrimeTester
return result; return result;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsPrimeGpu(ulong n) public static bool IsPrimeGpu(ulong n)
{ {
return Exclusive.IsPrimeGpu(n, CancellationToken.None); GpuPrimeWorkLimiter.Acquire();
var gpu = PrimeTesterGpuContextPool.Rent(1);
var accelerator = gpu.Accelerator;
var kernel = PrimeTesterGpuContextPool.PrimeTesterGpuContextLease.Kernel;
ulong value = n;
byte flag = 0;
var input = gpu.Input;
var output = gpu.Output;
var inputView = input.View;
var outputView = output.View;
inputView.CopyFromCPU(ref value, 1);
kernel(
1,
inputView,
gpu.DevicePrimesDefault.View,
gpu.DevicePrimesLastOne.View,
gpu.DevicePrimesLastSeven.View,
gpu.DevicePrimesLastThree.View,
gpu.DevicePrimesLastNine.View,
gpu.DevicePrimesPow2Default.View,
gpu.DevicePrimesPow2LastOne.View,
gpu.DevicePrimesPow2LastSeven.View,
gpu.DevicePrimesPow2LastThree.View,
gpu.DevicePrimesPow2LastNine.View,
outputView);
accelerator.Synchronize();
outputView.CopyToCPU(ref flag, 1);
gpu.Dispose();
GpuPrimeWorkLimiter.Release();
return flag != 0;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsPrimeGpu(ulong n, CancellationToken ct)
{
bool forceCpu = GpuContextPool.ForceCpu;
Span<ulong> one = stackalloc ulong[1];
Span<byte> outFlags = stackalloc byte[1];
one[0] = n;
outFlags[0] = 0;
if (!forceCpu)
{
IsPrimeBatchGpu(one, outFlags);
}
bool belowGpuRange = n < 31UL;
bool gpuReportedPrime = !forceCpu && !belowGpuRange && outFlags[0] != 0;
bool requiresCpuFallback = forceCpu || belowGpuRange || !gpuReportedPrime;
return requiresCpuFallback ? IsPrimeCpu(n, ct) : true;
}
public static bool IsPrimeCpu(ulong n, CancellationToken ct)
{
// Preserve the legacy entry point for callers that bypass the thread-local caches.
return Exclusive.IsPrime(n, ct);
}
public static int GpuBatchSize { get; set; } = 262_144; public static int GpuBatchSize { get; set; } = 262_144;
private static readonly object GpuWarmUpLock = new();
private static int WarmedGpuLeaseCount;
public static void WarmUpGpuKernels(int threadCount)
{
int target = threadCount >> 2;
if (target == 0)
{
target = threadCount;
}
lock (GpuWarmUpLock)
{
if (target <= WarmedGpuLeaseCount)
{
return;
}
PrimeTesterGpuContextPool.WarmUp(target, GpuBatchSize);
WarmedGpuLeaseCount = target;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IsPrimeBatchGpu(ReadOnlySpan<ulong> values, Span<byte> results) public static void IsPrimeBatchGpu(ReadOnlySpan<ulong> values, Span<byte> results)
{ {
var limiter = GpuPrimeWorkLimiter.Acquire(); GpuPrimeWorkLimiter.Acquire();
var gpu = PrimeTesterGpuContextPool.Rent(); var gpu = PrimeTesterGpuContextPool.Rent(GpuBatchSize);
var accelerator = gpu.Accelerator; var accelerator = gpu.Accelerator;
var state = GpuKernelState.GetOrCreate(accelerator); var kernel = PrimeTesterGpuContextPool.PrimeTesterGpuContextLease.Kernel;
int totalLength = values.Length; int totalLength = values.Length;
int batchSize = Math.Max(1, GpuBatchSize); int batchSize = GpuBatchSize;
lock (gpu.ExecutionLock) var input = gpu.Input;
{ var output = gpu.Output;
var scratch = state.RentScratch(batchSize, accelerator); var inputView = input.View;
var input = scratch.Input; var outputView = output.View;
var output = scratch.Output;
ArrayPool<ulong> pool = ThreadStaticPools.UlongPool;
ulong[] temp = pool.Rent(batchSize);
int pos = 0; int pos = 0;
while (pos < totalLength) while (pos < totalLength)
@@ -129,214 +178,526 @@ public sealed class PrimeTester
int remaining = totalLength - pos; int remaining = totalLength - pos;
int count = remaining > batchSize ? batchSize : remaining; int count = remaining > batchSize ? batchSize : remaining;
values.Slice(pos, count).CopyTo(temp); var valueSlice = values.Slice(pos, count);
input.View.CopyFromCPU(ref temp[0], count); ref ulong valueRef = ref MemoryMarshal.GetReference(valueSlice);
inputView.CopyFromCPU(ref valueRef, count);
state.Kernel(count, input.View, state.DevicePrimes.View, output.View); kernel(
count,
inputView,
gpu.DevicePrimesDefault.View,
gpu.DevicePrimesLastOne.View,
gpu.DevicePrimesLastSeven.View,
gpu.DevicePrimesLastThree.View,
gpu.DevicePrimesLastNine.View,
gpu.DevicePrimesPow2Default.View,
gpu.DevicePrimesPow2LastOne.View,
gpu.DevicePrimesPow2LastSeven.View,
gpu.DevicePrimesPow2LastThree.View,
gpu.DevicePrimesPow2LastNine.View,
outputView);
accelerator.Synchronize(); accelerator.Synchronize();
output.View.CopyToCPU(ref results[pos], count); var resultSlice = results.Slice(pos, count);
ref byte resultRef = ref MemoryMarshal.GetReference(resultSlice);
outputView.CopyToCPU(ref resultRef, count);
pos += count; pos += count;
} }
pool.Return(temp, clearArray: false);
state.ReturnScratch(scratch);
}
gpu.Dispose(); gpu.Dispose();
limiter.Dispose(); GpuPrimeWorkLimiter.Release();
} }
internal static class PrimeTesterGpuContextPool internal static class PrimeTesterGpuContextPool
{ {
internal sealed class PooledContext : IDisposable private static readonly ConcurrentQueue<PrimeTesterGpuContextLease> Pool = new();
private static readonly object WarmUpLock = new();
private static int WarmedLeaseCount;
private static readonly object SharedTablesLock = new();
private static readonly Dictionary<Accelerator, SharedHeuristicGpuTables> SharedTables = new(AcceleratorReferenceComparer.Instance);
private static SharedHeuristicGpuTables RentSharedTables(Accelerator accelerator)
{ {
private bool _disposed; lock (SharedTablesLock)
public Context Context { get; }
public Accelerator Accelerator { get; }
public object ExecutionLock { get; } = new();
public PooledContext()
{ {
Context = Context.CreateDefault(); if (!SharedTables.TryGetValue(accelerator, out var tables))
Accelerator = Context.GetPreferredDevice(false).CreateAccelerator(Context); {
tables = new SharedHeuristicGpuTables(accelerator);
SharedTables.Add(accelerator, tables);
} }
public void Dispose() tables.AddReference();
return tables;
}
}
private static void ReleaseSharedTables(SharedHeuristicGpuTables tables)
{ {
if (_disposed) if (tables is null)
{ {
return; return;
} }
PrimeTester.ClearGpuCaches(Accelerator); lock (SharedTablesLock)
Accelerator.Dispose(); {
Context.Dispose(); tables.Release();
_disposed = true;
} }
} }
private static readonly ConcurrentQueue<PooledContext> Pool = new(); internal static void EnsureStaticTables(KernelContainer kernels, Accelerator accelerator)
private static readonly object CreationLock = new();
internal static PrimeTesterGpuContextLease Rent()
{ {
if (Pool.TryDequeue(out var ctx)) var tables = RentSharedTables(accelerator);
{ var heuristicTables = tables.CreateHeuristicDivisorTables();
return new PrimeTesterGpuContextLease(ctx); HeuristicGpuDivisorTables.InitializeShared(in heuristicTables);
ReleaseSharedTables(tables);
} }
lock (CreationLock) internal static void WarmUp(int target, int minBufferCapacity)
{ {
return new PrimeTesterGpuContextLease(new PooledContext()); if (target <= 0)
{
return;
} }
lock (WarmUpLock)
{
if (target <= WarmedLeaseCount)
{
return;
}
KernelContainer kernels = GpuKernelPool.GetKernels();
GpuStaticTableInitializer.EnsureStaticTables(kernels, SharedGpuContext.Accelerator);
int toCreate = target - WarmedLeaseCount;
for (int i = 0; i < toCreate; i++)
{
Pool.Enqueue(new PrimeTesterGpuContextLease(minBufferCapacity));
}
kernels.Dispose();
WarmedLeaseCount = target;
}
}
internal static PrimeTesterGpuContextLease Rent(int minBufferCapacity = 1)
{
if (!Pool.TryDequeue(out var lease))
{
lease = new PrimeTesterGpuContextLease(minBufferCapacity);
}
else
{
lease.EnsureCapacity(minBufferCapacity);
}
lease.ResetReturnFlag();
return lease;
}
internal static void Return(PrimeTesterGpuContextLease lease)
{
lease.Accelerator.Synchronize();
Pool.Enqueue(lease);
} }
internal static void DisposeAll() internal static void DisposeAll()
{ {
while (Pool.TryDequeue(out var ctx)) while (Pool.TryDequeue(out var lease))
{ {
ctx.Dispose(); lease.DisposeResources();
}
lock (SharedTablesLock)
{
foreach (var entry in SharedTables.Values)
{
entry.Dispose();
}
SharedTables.Clear();
var emptyTables = default(HeuristicGpuDivisorTables);
HeuristicGpuDivisorTables.InitializeShared(in emptyTables);
}
WarmedLeaseCount = 0;
}
internal static void Clear(Accelerator accelerator)
{
if (accelerator is null)
{
return;
}
lock (WarmUpLock)
{
var retained = new List<PrimeTesterGpuContextLease>();
while (Pool.TryDequeue(out var lease))
{
if (lease.Accelerator == accelerator)
{
lease.DisposeResources();
}
else
{
retained.Add(lease);
} }
} }
private static void Return(PooledContext ctx) int retainedCount = retained.Count;
for (int i = 0; i < retainedCount; i++)
{ {
ctx.Accelerator.Synchronize(); Pool.Enqueue(retained[i]);
Pool.Enqueue(ctx);
} }
internal struct PrimeTesterGpuContextLease : IDisposable WarmedLeaseCount = Pool.Count;
{
private PooledContext? _ctx;
internal PrimeTesterGpuContextLease(PooledContext ctx)
{
_ctx = ctx;
} }
public Accelerator Accelerator => _ctx!.Accelerator; lock (SharedTablesLock)
public object ExecutionLock => _ctx!.ExecutionLock;
public void Dispose()
{ {
if (_ctx is { } ctx) if (SharedTables.Remove(accelerator, out var tables))
{ {
Return(ctx); tables.Dispose();
_ctx = null; }
if (SharedTables.Count == 0)
{
var emptyTables = default(HeuristicGpuDivisorTables);
HeuristicGpuDivisorTables.InitializeShared(in emptyTables);
} }
} }
} }
internal sealed class PrimeTesterGpuContextLease : IDisposable
{
private bool _returned;
private readonly SharedHeuristicGpuTables _sharedTables;
private readonly HeuristicGpuDivisorTables _heuristicDivisorTables;
internal readonly Context AcceleratorContext;
public readonly Accelerator Accelerator;
public static readonly Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>> Kernel = SharedGpuContext.Accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterKernels.SmallPrimeSieveKernel);
public static readonly Action<Index1D, ArrayView<int>, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables> HeuristicTrialDivisionKernel = SharedGpuContext.Accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<int>, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables>(PrimeTesterKernels.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;
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesDefault => _sharedTables.DevicePrimesDefault;
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastOne => _sharedTables.DevicePrimesLastOne;
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastSeven => _sharedTables.DevicePrimesLastSeven;
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastThree => _sharedTables.DevicePrimesLastThree;
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastNine => _sharedTables.DevicePrimesLastNine;
public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2Default => _sharedTables.DevicePrimesPow2Default;
public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastOne => _sharedTables.DevicePrimesPow2LastOne;
public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastSeven => _sharedTables.DevicePrimesPow2LastSeven;
public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastThree => _sharedTables.DevicePrimesPow2LastThree;
public MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastNine => _sharedTables.DevicePrimesPow2LastNine;
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;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding1 => _sharedTables.HeuristicCombinedDivisorsEnding1;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding1 => _sharedTables.HeuristicCombinedDivisorSquaresEnding1;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding3 => _sharedTables.HeuristicCombinedDivisorsEnding3;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding3 => _sharedTables.HeuristicCombinedDivisorSquaresEnding3;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding7 => _sharedTables.HeuristicCombinedDivisorsEnding7;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding7 => _sharedTables.HeuristicCombinedDivisorSquaresEnding7;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding9 => _sharedTables.HeuristicCombinedDivisorsEnding9;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding9 => _sharedTables.HeuristicCombinedDivisorSquaresEnding9;
public HeuristicGpuDivisorTables HeuristicGpuTables => _heuristicDivisorTables;
internal PrimeTesterGpuContextLease(int minBufferCapacity)
{
AcceleratorContext = SharedGpuContext.Context;
Accelerator = SharedGpuContext.Accelerator;
// GpuStaticTableInitializer.EnsureStaticTables(Accelerator);
// Kernel = Accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterKernels.SmallPrimeSieveKernel);
// HeuristicTrialDivisionKernel = Accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<int>, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGpuDivisorTables>(PrimeTesterKernels.HeuristicTrialDivisionKernel);
_sharedTables = RentSharedTables(Accelerator);
_heuristicDivisorTables = _sharedTables.CreateHeuristicDivisorTables();
HeuristicGpuDivisorTables.InitializeShared(in _heuristicDivisorTables);
EnsureCapacity(minBufferCapacity);
_returned = false;
} }
// Per-accelerator GPU state for prime sieve (kernel + uploaded primes). public void EnsureCapacity(int minCapacity)
internal sealed class KernelState
{ {
public Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>> Kernel { get; } if (minCapacity <= BufferCapacity)
public Action<Index1D, ArrayView<ulong>, ulong, ArrayView<byte>> HeuristicTrialDivisionKernel { get; }
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimes { get; }
private readonly Accelerator _accel;
private readonly System.Collections.Concurrent.ConcurrentBag<ScratchBuffers> _scratchPool = [];
// TODO: Replace this ConcurrentBag with the lock-free ring buffer variant validated in
// GpuModularArithmeticBenchmarks so renting scratch buffers stops contending on the bag's internal locks when
// thousands of GPU batches execute per second.
public KernelState(Accelerator accelerator)
{ {
_accel = accelerator; return;
// Compile once per accelerator and upload primes once.
Kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>>(PrimeTesterKernels.SmallPrimeSieveKernel);
HeuristicTrialDivisionKernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ulong, ArrayView<byte>>(PrimeTesterKernels.HeuristicTrialDivisionKernel);
var primes = PrimesGenerator.SmallPrimes;
DevicePrimes = accelerator.Allocate1D<uint>(primes.Length);
DevicePrimes.View.CopyFromCPU(primes);
} }
internal sealed class ScratchBuffers : IDisposable Input?.Dispose();
{ Output?.Dispose();
public MemoryBuffer1D<ulong, Stride1D.Dense> Input { get; private set; } HeuristicFlag?.Dispose();
public MemoryBuffer1D<byte, Stride1D.Dense> Output { get; private set; } Input = Accelerator.Allocate1D<ulong>(minCapacity);
public int Capacity { get; private set; } Output = Accelerator.Allocate1D<byte>(minCapacity);
HeuristicFlag = Accelerator.Allocate1D<int>(minCapacity);
BufferCapacity = minCapacity;
}
public ScratchBuffers(Accelerator accel, int capacity) internal void ResetReturnFlag()
{ {
Capacity = Math.Max(1, capacity); _returned = false;
Input = accel.Allocate1D<ulong>(Capacity);
Output = accel.Allocate1D<byte>(Capacity);
} }
public void Dispose() public void Dispose()
{ {
Output.Dispose(); if (_returned)
Input.Dispose(); {
return;
}
_returned = true;
Return(this);
}
internal void DisposeResources()
{
Input?.Dispose();
Output?.Dispose();
HeuristicFlag?.Dispose();
ReleaseSharedTables(_sharedTables);
// These resources are shared between GPU leases
// Accelerator.Dispose();
// AcceleratorContext.Dispose();
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
public ScratchBuffers RentScratch(int minCapacity, Accelerator accel)
{ {
minCapacity = Math.Max(1, minCapacity); internal static AcceleratorReferenceComparer Instance { get; } = new();
while (_scratchPool.TryTake(out var sb))
public bool Equals(Accelerator? x, Accelerator? y)
{ {
if (sb.Capacity >= minCapacity) return ReferenceEquals(x, y);
{
return sb;
} }
sb.Dispose(); public int GetHashCode(Accelerator obj)
}
return new ScratchBuffers(accel, minCapacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReturnScratch(ScratchBuffers scratch)
{ {
_scratchPool.Add(scratch); return RuntimeHelpers.GetHashCode(obj);
}
public void Clear()
{
while (_scratchPool.TryTake(out var sb))
{
sb.Dispose();
}
DevicePrimes.Dispose();
} }
} }
internal static class GpuKernelState private sealed class SharedHeuristicGpuTables
{ {
// Map accelerator to cached state; use Lazy to serialize kernel creation private int _referenceCount;
private static readonly System.Collections.Concurrent.ConcurrentDictionary<Accelerator, Lazy<KernelState>> States = new();
// TODO: Prewarm this per-accelerator cache during startup (and reuse a simple array keyed by accelerator index)
// once the kernel pool exposes deterministic ordering; the Lazy wrappers showed measurable overhead in the
// GpuModularArithmeticBenchmarks hot path.
[MethodImpl(MethodImplOptions.AggressiveInlining)] internal SharedHeuristicGpuTables(Accelerator accelerator)
public static KernelState GetOrCreate(Accelerator accelerator)
{ {
var lazy = States.GetOrAdd(
accelerator, var primesDefault = DivisorGenerator.SmallPrimes;
acc => new Lazy<KernelState>(() => new KernelState(acc), System.Threading.LazyThreadSafetyMode.ExecutionAndPublication)); DevicePrimesDefault = accelerator.Allocate1D<uint>(primesDefault.Length);
return lazy.Value; DevicePrimesDefault.View.CopyFromCPU(primesDefault);
var primesLastOne = DivisorGenerator.SmallPrimesLastOne;
DevicePrimesLastOne = accelerator.Allocate1D<uint>(primesLastOne.Length);
DevicePrimesLastOne.View.CopyFromCPU(primesLastOne);
var primesLastSeven = DivisorGenerator.SmallPrimesLastSeven;
DevicePrimesLastSeven = accelerator.Allocate1D<uint>(primesLastSeven.Length);
DevicePrimesLastSeven.View.CopyFromCPU(primesLastSeven);
var primesLastThree = DivisorGenerator.SmallPrimesLastThree;
DevicePrimesLastThree = accelerator.Allocate1D<uint>(primesLastThree.Length);
DevicePrimesLastThree.View.CopyFromCPU(primesLastThree);
var primesLastNine = DivisorGenerator.SmallPrimesLastNine;
DevicePrimesLastNine = accelerator.Allocate1D<uint>(primesLastNine.Length);
DevicePrimesLastNine.View.CopyFromCPU(primesLastNine);
var primesPow2Default = DivisorGenerator.SmallPrimesPow2;
DevicePrimesPow2Default = accelerator.Allocate1D<ulong>(primesPow2Default.Length);
DevicePrimesPow2Default.View.CopyFromCPU(primesPow2Default);
var primesPow2LastOne = DivisorGenerator.SmallPrimesPow2LastOne;
DevicePrimesPow2LastOne = accelerator.Allocate1D<ulong>(primesPow2LastOne.Length);
DevicePrimesPow2LastOne.View.CopyFromCPU(primesPow2LastOne);
var primesPow2LastSeven = DivisorGenerator.SmallPrimesPow2LastSeven;
DevicePrimesPow2LastSeven = accelerator.Allocate1D<ulong>(primesPow2LastSeven.Length);
DevicePrimesPow2LastSeven.View.CopyFromCPU(primesPow2LastSeven);
var primesPow2LastThree = DivisorGenerator.SmallPrimesPow2LastThree;
DevicePrimesPow2LastThree = accelerator.Allocate1D<ulong>(primesPow2LastThree.Length);
DevicePrimesPow2LastThree.View.CopyFromCPU(primesPow2LastThree);
var primesPow2LastNine = DivisorGenerator.SmallPrimesPow2LastNine;
DevicePrimesPow2LastNine = accelerator.Allocate1D<ulong>(primesPow2LastNine.Length);
DevicePrimesPow2LastNine.View.CopyFromCPU(primesPow2LastNine);
HeuristicPrimeSieves.EnsureInitialized();
HeuristicCombinedPrimeTester.EnsureInitialized();
var heuristicGroupA = HeuristicPrimeSieves.GroupADivisors;
HeuristicGroupADivisors = CopySpanToDevice(accelerator, heuristicGroupA);
var heuristicGroupASquares = HeuristicPrimeSieves.GroupADivisorSquares;
HeuristicGroupADivisorSquares = CopySpanToDevice(accelerator, heuristicGroupASquares);
HeuristicGroupBDivisorsEnding1 = CopyUintSpanToDevice(accelerator, DivisorGenerator.SmallPrimesLastOneWithoutLastThree);
HeuristicGroupBDivisorSquaresEnding1 = CopySpanToDevice(accelerator, DivisorGenerator.SmallPrimesPow2LastOneWithoutLastThree);
HeuristicGroupBDivisorsEnding7 = CopyUintSpanToDevice(accelerator, DivisorGenerator.SmallPrimesLastSevenWithoutLastThree);
HeuristicGroupBDivisorSquaresEnding7 = CopySpanToDevice(accelerator, DivisorGenerator.SmallPrimesPow2LastSevenWithoutLastThree);
HeuristicGroupBDivisorsEnding9 = CopyUintSpanToDevice(accelerator, DivisorGenerator.SmallPrimesLastNineWithoutLastThree);
HeuristicGroupBDivisorSquaresEnding9 = CopySpanToDevice(accelerator, DivisorGenerator.SmallPrimesPow2LastNineWithoutLastThree);
var combinedEnding1 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding1Span;
HeuristicCombinedDivisorsEnding1 = CopySpanToDevice(accelerator, combinedEnding1);
var combinedSquaresEnding1 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding1SquaresSpan;
HeuristicCombinedDivisorSquaresEnding1 = CopySpanToDevice(accelerator, combinedSquaresEnding1);
var combinedEnding3 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding3Span;
HeuristicCombinedDivisorsEnding3 = CopySpanToDevice(accelerator, combinedEnding3);
var combinedSquaresEnding3 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding3SquaresSpan;
HeuristicCombinedDivisorSquaresEnding3 = CopySpanToDevice(accelerator, combinedSquaresEnding3);
var combinedEnding7 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding7Span;
HeuristicCombinedDivisorsEnding7 = CopySpanToDevice(accelerator, combinedEnding7);
var combinedSquaresEnding7 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding7SquaresSpan;
HeuristicCombinedDivisorSquaresEnding7 = CopySpanToDevice(accelerator, combinedSquaresEnding7);
var combinedEnding9 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding9Span;
HeuristicCombinedDivisorsEnding9 = CopySpanToDevice(accelerator, combinedEnding9);
var combinedSquaresEnding9 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding9SquaresSpan;
HeuristicCombinedDivisorSquaresEnding9 = CopySpanToDevice(accelerator, combinedSquaresEnding9);
} }
public static void Clear(Accelerator accelerator) internal MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesDefault { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastOne { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastSeven { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastThree { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastNine { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2Default { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastOne { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastSeven { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastThree { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastNine { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisors { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisorSquares { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding1 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding1 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding7 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding7 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding9 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding9 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding1 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding1 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding3 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding3 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding7 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding7 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding9 { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding9 { get; }
internal HeuristicGpuDivisorTables CreateHeuristicDivisorTables()
{ {
if (States.TryRemove(accelerator, out var lazy)) return new HeuristicGpuDivisorTables(
{ HeuristicCombinedDivisorsEnding1.View,
if (lazy.IsValueCreated) HeuristicCombinedDivisorsEnding3.View,
{ HeuristicCombinedDivisorsEnding7.View,
var state = lazy.Value; HeuristicCombinedDivisorsEnding9.View,
state.Clear(); HeuristicCombinedDivisorSquaresEnding1.View,
HeuristicCombinedDivisorSquaresEnding3.View,
HeuristicCombinedDivisorSquaresEnding7.View,
HeuristicCombinedDivisorSquaresEnding9.View,
HeuristicGroupADivisors.View,
HeuristicGroupADivisorSquares.View,
HeuristicGroupBDivisorsEnding1.View,
HeuristicGroupBDivisorSquaresEnding1.View,
HeuristicGroupBDivisorsEnding7.View,
HeuristicGroupBDivisorSquaresEnding7.View,
HeuristicGroupBDivisorsEnding9.View,
HeuristicGroupBDivisorSquaresEnding9.View);
} }
internal void AddReference()
{
_referenceCount++;
}
internal void Release()
{
if (_referenceCount > 0)
{
_referenceCount--;
}
}
private static MemoryBuffer1D<ulong, Stride1D.Dense> CopySpanToDevice(Accelerator accelerator, ReadOnlySpan<ulong> span)
{
var buffer = accelerator.Allocate1D<ulong>(span.Length);
if (!span.IsEmpty)
{
ref ulong sourceRef = ref MemoryMarshal.GetReference(span);
buffer.View.CopyFromCPU(ref sourceRef, span.Length);
}
return buffer;
}
private static MemoryBuffer1D<ulong, Stride1D.Dense> CopyUintSpanToDevice(Accelerator accelerator, ReadOnlySpan<uint> span)
{
var 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(converted);
}
return buffer;
}
internal void Dispose()
{
DevicePrimesDefault.Dispose();
DevicePrimesLastOne.Dispose();
DevicePrimesLastSeven.Dispose();
DevicePrimesLastThree.Dispose();
DevicePrimesLastNine.Dispose();
DevicePrimesPow2Default.Dispose();
DevicePrimesPow2LastOne.Dispose();
DevicePrimesPow2LastSeven.Dispose();
DevicePrimesPow2LastThree.Dispose();
DevicePrimesPow2LastNine.Dispose();
HeuristicGroupADivisors.Dispose();
HeuristicGroupADivisorSquares.Dispose();
HeuristicGroupBDivisorsEnding1.Dispose();
HeuristicGroupBDivisorSquaresEnding1.Dispose();
HeuristicGroupBDivisorsEnding7.Dispose();
HeuristicGroupBDivisorSquaresEnding7.Dispose();
HeuristicGroupBDivisorsEnding9.Dispose();
HeuristicGroupBDivisorSquaresEnding9.Dispose();
HeuristicCombinedDivisorsEnding1.Dispose();
HeuristicCombinedDivisorSquaresEnding1.Dispose();
HeuristicCombinedDivisorsEnding3.Dispose();
HeuristicCombinedDivisorSquaresEnding3.Dispose();
HeuristicCombinedDivisorsEnding7.Dispose();
HeuristicCombinedDivisorSquaresEnding7.Dispose();
HeuristicCombinedDivisorsEnding9.Dispose();
HeuristicCombinedDivisorSquaresEnding9.Dispose();
} }
} }
} }
@@ -344,12 +705,16 @@ public sealed class PrimeTester
// Expose cache clearing for accelerator disposal coordination // Expose cache clearing for accelerator disposal coordination
public static void ClearGpuCaches(Accelerator accelerator) public static void ClearGpuCaches(Accelerator accelerator)
{ {
GpuKernelState.Clear(accelerator); PrimeTesterGpuContextPool.Clear(accelerator);
} }
internal static void DisposeGpuContexts() internal static void DisposeGpuContexts()
{ {
PrimeTesterGpuContextPool.DisposeAll(); PrimeTesterGpuContextPool.DisposeAll();
lock (GpuWarmUpLock)
{
WarmedGpuLeaseCount = 0;
}
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -421,4 +786,4 @@ public sealed class PrimeTester
return u << shift; return u << shift;
} }
} }
+6 -146
View File
@@ -1,151 +1,11 @@
using System;
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core; namespace PerfectNumbers.Core;
public static class PrimesGenerator public static class PrimesGenerator
{ {
public static readonly uint[] SmallPrimes; public static readonly uint[] SmallPrimes = DivisorGenerator.SmallPrimes;
public static readonly ulong[] SmallPrimesPow2; public static readonly ulong[] SmallPrimesPow2 = DivisorGenerator.SmallPrimesPow2;
public static readonly uint[] SmallPrimesLastOne; public static readonly uint[] SmallPrimesLastOne = DivisorGenerator.SmallPrimesLastOne;
public static readonly ulong[] SmallPrimesPow2LastOne; public static readonly ulong[] SmallPrimesPow2LastOne = DivisorGenerator.SmallPrimesPow2LastOne;
public static readonly uint[] SmallPrimesLastSeven; public static readonly uint[] SmallPrimesLastSeven = DivisorGenerator.SmallPrimesLastSeven;
public static readonly ulong[] SmallPrimesPow2LastSeven; public static readonly ulong[] SmallPrimesPow2LastSeven = DivisorGenerator.SmallPrimesPow2LastSeven;
static PrimesGenerator()
{
BuildSmallPrimes(
out SmallPrimes,
out SmallPrimesPow2,
out SmallPrimesLastOne,
out SmallPrimesPow2LastOne,
out SmallPrimesLastSeven,
out SmallPrimesPow2LastSeven);
}
private static void BuildSmallPrimes(
out uint[] all,
out ulong[] allPow2,
out uint[] lastOne,
out ulong[] lastOnePow2,
out uint[] lastSeven,
out ulong[] lastSevenPow2)
{
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];
List<uint> primes = new(targetInt * 4 / 3 + 16);
uint candidate = 2U;
int allCount = 0;
int lastOneCount = 0;
int lastSevenCount = 0;
while (allCount < targetInt || lastOneCount < targetInt || lastSevenCount < targetInt)
{
bool isPrime = true;
int primesCount = primes.Count;
for (int i = 0; i < primesCount; i++)
{
uint p = primes[i];
// TODO: Reuse a single squared local for `p` within this loop so we avoid recalculating
// `p * p` repeatedly without storing every squared prime globally (the cache must remain
// limited to the small-divisor tables to honor the memory constraints).
if (p * p > candidate)
{
break;
}
// TODO: Replace this trial-division `%` with the sieve-based generator that avoids
// per-candidate modulo work so building the small-prime tables stops dominating
// startup time for large scans.
if (candidate % p == 0U)
{
isPrime = false;
break;
}
}
if (isPrime)
{
primes.Add(candidate);
if (allCount < targetInt)
{
// TODO: Reuse a single cached `candidateSquared` per iteration so we avoid issuing
// three separate 64-bit multiplications when populating the pow2 arrays here and
// in the last-one/last-seven branches below.
all[allCount] = candidate;
allPow2[allCount] = checked((ulong)candidate * candidate);
allCount++;
}
if (candidate != 2U)
{
LastDigit lastDigit = GetLastDigit(candidate);
if (lastOneCount < targetInt && IsAllowedForLastOne(candidate, lastDigit))
{
lastOne[lastOneCount] = candidate;
lastOnePow2[lastOneCount] = checked((ulong)candidate * candidate);
lastOneCount++;
}
if (lastSevenCount < targetInt && IsAllowedForLastSeven(candidate, lastDigit))
{
lastSeven[lastSevenCount] = candidate;
lastSevenPow2[lastSevenCount] = checked((ulong)candidate * candidate);
lastSevenCount++;
}
}
}
// TODO: Switch this increment to the Mod6 stepping table identified in the Mod6ComparisonBenchmarks
// so we skip the composite residues without relying on per-iteration `%` checks.
candidate = candidate == 2U ? 3U : candidate + 2U;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static LastDigit GetLastDigit(uint prime)
{
return (prime % 10U) switch
{
1U => LastDigit.One,
3U => LastDigit.Three,
5U => LastDigit.Five,
7U => LastDigit.Seven,
9U => LastDigit.Nine,
_ => throw new ArgumentOutOfRangeException(nameof(prime)),
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsAllowedForLastOne(uint prime, LastDigit lastDigit)
{
// TODO: Swap the `% 10` usage for ULongExtensions.Mod10 so the hot classification path
// reuses the benchmarked residue helper instead of repeated divisions.
return lastDigit switch
{
LastDigit.One or LastDigit.Three or LastDigit.Nine => true,
LastDigit.Seven => prime == 7U,
_ => prime == 11U,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsAllowedForLastSeven(uint prime, LastDigit lastDigit)
{
// TODO: Route this `% 10` classification through ULongExtensions.Mod10 to match the faster
// residue helper used elsewhere in the scanner.
return lastDigit switch
{
LastDigit.Three or LastDigit.Seven or LastDigit.Nine => true,
_ => prime == 11U,
};
}
} }
+147 -31
View File
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace PerfectNumbers.Core; namespace PerfectNumbers.Core;
internal sealed class TaskThreadPool : IDisposable internal sealed class TaskThreadPool
{ {
private readonly struct WorkItem private readonly struct WorkItem
{ {
@@ -19,10 +19,15 @@ internal sealed class TaskThreadPool : IDisposable
} }
private readonly ConcurrentQueue<WorkItem> _pendingTaskQueue = new(); private readonly ConcurrentQueue<WorkItem> _pendingTaskQueue = new();
private readonly BlockingCollection<WorkItem> _pendingTasks; private readonly HashSet<Thread> _runningThreads = new();
private readonly Thread[] _threads; private readonly object _lock = new();
private readonly Action<Task> _taskExecutor; private readonly Action<Task> _taskExecutor;
private int _disposed; private readonly int _maximumConcurrency;
private readonly SemaphoreSlim _workAvailable = new(0);
private readonly CancellationTokenSource _disposeCancellation = new();
private int _pendingTaskCount;
private int _currentThreads;
private int _threadId;
public TaskThreadPool(int minimumThreads, Action<Task> taskExecutor) public TaskThreadPool(int minimumThreads, Action<Task> taskExecutor)
{ {
@@ -32,23 +37,28 @@ internal sealed class TaskThreadPool : IDisposable
} }
_taskExecutor = taskExecutor ?? throw new ArgumentNullException(nameof(taskExecutor)); _taskExecutor = taskExecutor ?? throw new ArgumentNullException(nameof(taskExecutor));
_pendingTasks = new BlockingCollection<WorkItem>(_pendingTaskQueue); _maximumConcurrency = minimumThreads;
_threads = new Thread[minimumThreads];
List<Thread> threadsToStart = new(minimumThreads);
lock (_lock)
{
for (int i = 0; i < minimumThreads; i++) for (int i = 0; i < minimumThreads; i++)
{ {
Thread thread = new(WorkerLoop) Thread thread = CreateWorkerThread();
{ _runningThreads.Add(thread);
IsBackground = true, _currentThreads++;
Name = $"{nameof(TaskThreadPool)}-{i + 1}" threadsToStart.Add(thread);
}; }
}
_threads[i] = thread; foreach (Thread thread in threadsToStart)
{
thread.Start(); thread.Start();
} }
} }
public int ThreadCount => _threads.Length; public int ThreadCount => _maximumConcurrency;
public void Queue(Task task) public void Queue(Task task)
{ {
@@ -57,22 +67,29 @@ internal sealed class TaskThreadPool : IDisposable
throw new ArgumentNullException(nameof(task)); throw new ArgumentNullException(nameof(task));
} }
if (Volatile.Read(ref _disposed) != 0) List<Thread>? threadsToStart;
lock (_lock)
{
if (_disposeCancellation.IsCancellationRequested)
{ {
throw new ObjectDisposedException(nameof(TaskThreadPool)); throw new ObjectDisposedException(nameof(TaskThreadPool));
} }
try _pendingTaskQueue.Enqueue(new WorkItem(task));
{ _pendingTaskCount++;
_pendingTasks.Add(new WorkItem(task));
threadsToStart = EnsureWorkerCapacityLocked(null);
} }
catch (InvalidOperationException ex) when (Volatile.Read(ref _disposed) != 0)
_workAvailable.Release();
if (threadsToStart is not null)
{ {
throw new ObjectDisposedException(nameof(TaskThreadPool), ex); foreach (Thread thread in threadsToStart)
{
thread.Start();
} }
catch (ObjectDisposedException ex)
{
throw new ObjectDisposedException(nameof(TaskThreadPool), ex);
} }
} }
@@ -97,33 +114,132 @@ internal sealed class TaskThreadPool : IDisposable
public void Dispose() public void Dispose()
{ {
if (Interlocked.Exchange(ref _disposed, 1) != 0) Thread[] threadsToJoin;
lock (_lock)
{ {
return; _disposeCancellation.Cancel();
threadsToJoin = new Thread[_runningThreads.Count];
_runningThreads.CopyTo(threadsToJoin);
} }
_pendingTasks.CompleteAdding(); if (threadsToJoin.Length > 0)
{
_workAvailable.Release(threadsToJoin.Length);
}
foreach (Thread thread in _threads) foreach (Thread thread in threadsToJoin)
{ {
thread.Join(); thread.Join();
} }
_pendingTasks.Dispose(); _workAvailable.Dispose();
_disposeCancellation.Dispose();
lock (_lock)
{
_runningThreads.Clear();
while (_pendingTaskQueue.TryDequeue(out _))
{
}
_pendingTaskCount = 0;
_currentThreads = 0;
}
}
private bool TryDequeueWork(out WorkItem workItem)
{
CancellationToken cancellationToken = _disposeCancellation.Token;
while (true)
{
try
{
_workAvailable.Wait(cancellationToken);
}
catch (OperationCanceledException)
{
workItem = default;
return false;
}
if (_pendingTaskQueue.TryDequeue(out workItem))
{
Interlocked.Decrement(ref _pendingTaskCount);
return true;
}
}
} }
private void WorkerLoop() private void WorkerLoop()
{ {
try try
{ {
foreach (WorkItem workItem in _pendingTasks.GetConsumingEnumerable()) if (!TryDequeueWork(out WorkItem workItem))
{ {
return;
}
_taskExecutor(workItem.Task); _taskExecutor(workItem.Task);
} }
} finally
catch (ObjectDisposedException)
{ {
// The collection was disposed while the worker was waiting. Exit gracefully. CompleteWorker();
} }
} }
private void CompleteWorker()
{
List<Thread>? threadsToStart = null;
lock (_lock)
{
_runningThreads.Remove(Thread.CurrentThread);
_currentThreads--;
if (!_disposeCancellation.IsCancellationRequested)
{
threadsToStart = EnsureWorkerCapacityLocked(null);
}
}
if (threadsToStart is not null)
{
foreach (Thread thread in threadsToStart)
{
thread.Start();
}
}
}
private List<Thread>? EnsureWorkerCapacityLocked(List<Thread>? threadsToStart)
{
int pendingTasks = Math.Min(_maximumConcurrency, Volatile.Read(ref _pendingTaskCount));
while (!_disposeCancellation.IsCancellationRequested && _currentThreads < pendingTasks)
{
Thread thread = CreateWorkerThread();
_runningThreads.Add(thread);
_currentThreads++;
threadsToStart ??= new List<Thread>();
threadsToStart.Add(thread);
}
return threadsToStart;
}
private Thread CreateWorkerThread()
{
int threadIndex = Interlocked.Increment(ref _threadId);
return new Thread(WorkerLoop)
{
IsBackground = true,
Name = $"{nameof(TaskThreadPool)}-{threadIndex}"
};
}
} }
+1 -1
View File
@@ -6,7 +6,7 @@ namespace PerfectNumbers.Core;
/// <summary> /// <summary>
/// Provides thread-safe file writing for both text and binary data. /// Provides thread-safe file writing for both text and binary data.
/// </summary> /// </summary>
public sealed class TextFileWriter : IDisposable public sealed class TextFileWriter
{ {
private readonly FileStream _stream; private readonly FileStream _stream;
private StreamWriter? _textWriter; private StreamWriter? _textWriter;
-37
View File
@@ -260,43 +260,6 @@ namespace PerfectNumbers.Core
pool.Add(stack); pool.Add(stack);
} }
[ThreadStatic]
private static List<Dictionary<ulong, int>>? _ulongIntDictionaryPool;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Dictionary<ulong, int> RentUlongIntDictionary(int capacityHint)
{
List<Dictionary<ulong, int>>? pool = _ulongIntDictionaryPool;
if (pool is not null)
{
int poolCount = pool.Count;
if (poolCount > 0)
{
int lastIndex = poolCount - 1;
Dictionary<ulong, int> dictionary = pool[lastIndex];
pool.RemoveAt(lastIndex);
dictionary.EnsureCapacity(capacityHint);
return dictionary;
}
}
return new Dictionary<ulong, int>(capacityHint);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReturnUlongIntDictionary(Dictionary<ulong, int> dictionary)
{
List<Dictionary<ulong, int>>? pool = _ulongIntDictionaryPool;
if (pool is null)
{
pool = new List<Dictionary<ulong, int>>(4);
_ulongIntDictionaryPool = pool;
}
pool.Add(dictionary);
}
[ThreadStatic] [ThreadStatic]
private static Dictionary<ulong, int>? _factorCountDictionary; private static Dictionary<ulong, int>? _factorCountDictionary;
+5 -5
View File
@@ -40,7 +40,7 @@ public static partial class ULongExtensions
{ {
if (((exponent >> index) & 1UL) == 0UL) if (((exponent >> index) & 1UL) == 0UL)
{ {
result = result.MontgomeryMultiply(result, modulus, nPrime); result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
index--; index--;
continue; continue;
} }
@@ -59,14 +59,14 @@ public static partial class ULongExtensions
int windowLength = index - windowStart + 1; int windowLength = index - windowStart + 1;
for (int square = 0; square < windowLength; square++) for (int square = 0; square < windowLength; square++)
{ {
result = result.MontgomeryMultiply(result, modulus, nPrime); result = result.MontgomeryMultiplyCpu(result, modulus, nPrime);
} }
ulong mask = (1UL << windowLength) - 1UL; ulong mask = (1UL << windowLength) - 1UL;
ulong windowValue = (exponent >> windowStart) & mask; ulong windowValue = (exponent >> windowStart) & mask;
int tableIndex = (int)((windowValue - 1UL) >> 1); int tableIndex = (int)((windowValue - 1UL) >> 1);
ulong multiplier = oddPowers[tableIndex]; ulong multiplier = oddPowers[tableIndex];
result = result.MontgomeryMultiply(multiplier, modulus, nPrime); result = result.MontgomeryMultiplyCpu(multiplier, modulus, nPrime);
index = windowStart - 1; index = windowStart - 1;
} }
@@ -75,7 +75,7 @@ public static partial class ULongExtensions
if (!keepMontgomery) if (!keepMontgomery)
{ {
result = result.MontgomeryMultiply(1UL, modulus, nPrime); result = result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
} }
return result; return result;
@@ -96,7 +96,7 @@ public static partial class ULongExtensions
for (int i = 1; i < oddPowers.Length; i++) for (int i = 1; i < oddPowers.Length; i++)
{ {
previous = oddPowers[i - 1]; previous = oddPowers[i - 1];
oddPowers[i] = previous.MontgomeryMultiply(square, modulus, nPrime); oddPowers[i] = previous.MontgomeryMultiplyCpu(square, modulus, nPrime);
} }
} }
+47 -9
View File
@@ -46,6 +46,45 @@ public static partial class ULongExtensions
return state.MulMod(right, modulus); return state.MulMod(right, modulus);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ulong MontgomeryMultiplyGpu(this ulong a, ulong b, ulong modulus, ulong nPrime)
{
// Mirror the struct-based MontMul64 helper measured as the second-fastest GPU-compatible
// option in GpuUInt128Montgomery64Benchmarks so accelerator kernels reuse the validated path.
MultiplyPartsGpu(a, b, out ulong productHigh, out ulong productLow);
MultiplyPartsGpu(productLow, nPrime, out _, out ulong mLow);
MultiplyPartsGpu(mLow, modulus, out ulong mTimesModulusHigh, out ulong mTimesModulusLow);
ulong sumLow = unchecked(productLow + mTimesModulusLow);
ulong carry = sumLow < productLow ? 1UL : 0UL;
ulong sumHigh = unchecked(productHigh + mTimesModulusHigh + carry);
if (sumHigh >= modulus)
{
sumHigh -= modulus;
}
return sumHigh;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void MultiplyPartsGpu(ulong left, ulong right, out ulong high, out ulong low)
{
ulong leftLow = (uint)left;
ulong leftHigh = left >> 32;
ulong rightLow = (uint)right;
ulong rightHigh = right >> 32;
ulong lowProduct = unchecked(leftLow * rightLow);
ulong cross1 = unchecked(leftHigh * rightLow);
ulong cross2 = unchecked(leftLow * rightHigh);
ulong highProduct = unchecked(leftHigh * rightHigh);
ulong carry = unchecked((lowProduct >> 32) + (uint)cross1 + (uint)cross2);
low = unchecked((lowProduct & 0xFFFFFFFFUL) | (carry << 32));
high = unchecked(highProduct + (cross1 >> 32) + (cross2 >> 32) + (carry >> 32));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Pow2ModWindowedGpu(this ulong exponent, ulong modulus) public static ulong Pow2ModWindowedGpu(this ulong exponent, ulong modulus)
{ {
@@ -204,14 +243,15 @@ public static partial class ULongExtensions
ulong modulus = divisor.Modulus; ulong modulus = divisor.Modulus;
ulong nPrime = divisor.NPrime; ulong nPrime = divisor.NPrime;
ulong montgomeryResult = Pow2MontgomeryModWindowedGpuMontgomeryResult(divisor, exponent); ulong montgomeryResult = Pow2MontgomeryModWindowedGpuMontgomeryResult(divisor, exponent);
return MontgomeryMultiply(montgomeryResult, 1UL, modulus, nPrime); return MontgomeryMultiplyGpu(montgomeryResult, 1UL, modulus, nPrime);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong Pow2MontgomeryModWindowedGpuMontgomeryResult(in MontgomeryDivisorData divisor, ulong exponent) private static ulong Pow2MontgomeryModWindowedGpuMontgomeryResult(in MontgomeryDivisorData divisor, ulong exponent)
{ {
ulong modulus = divisor.Modulus; ulong modulus = divisor.Modulus;
// Reuse the optimized MontgomeryMultiply extension highlighted in MontgomeryMultiplyBenchmarks so the GPU path matches the fastest CPU reduction. // Reuse the GPU-compatible MontgomeryMultiply helper highlighted in MontgomeryMultiplyBenchmarks so the accelerator path
// mirrors the fastest CPU reduction without relying on UInt128 intrinsics.
// We shouldn't hit it in production code. If we do, we're doing something wrong. // We shouldn't hit it in production code. If we do, we're doing something wrong.
// if (exponent == 0UL) // if (exponent == 0UL)
// { // {
@@ -233,7 +273,7 @@ public static partial class ULongExtensions
while (index >= 0) while (index >= 0)
{ {
ulong currentBit = (exponent >> index) & 1UL; ulong currentBit = (exponent >> index) & 1UL;
ulong squared = MontgomeryMultiply(result, result, modulus, nPrime); ulong squared = MontgomeryMultiplyGpu(result, result, modulus, nPrime);
bool processWindow = currentBit != 0UL; bool processWindow = currentBit != 0UL;
result = processWindow ? result : squared; result = processWindow ? result : squared;
@@ -253,11 +293,11 @@ public static partial class ULongExtensions
int windowLength = processWindow ? index - windowStart + 1 : 0; int windowLength = processWindow ? index - windowStart + 1 : 0;
for (int square = 0; square < windowLength; square++) for (int square = 0; square < windowLength; square++)
{ {
result = MontgomeryMultiply(result, result, modulus, nPrime); result = MontgomeryMultiplyGpu(result, result, modulus, nPrime);
} }
result = processWindow result = processWindow
? MontgomeryMultiply( ? MontgomeryMultiplyGpu(
result, result,
ComputeMontgomeryOddPowerGpu( ComputeMontgomeryOddPowerGpu(
(exponent >> windowStart) & ((1UL << windowLength) - 1UL), (exponent >> windowStart) & ((1UL << windowLength) - 1UL),
@@ -330,7 +370,7 @@ public static partial class ULongExtensions
while (remaining != 0UL) while (remaining != 0UL)
{ {
ulong bit = remaining & 1UL; ulong bit = remaining & 1UL;
ulong multiplied = MontgomeryMultiply(power, baseValue, modulus, nPrime); ulong multiplied = MontgomeryMultiplyGpu(power, baseValue, modulus, nPrime);
ulong mask = (ulong)-(long)bit; ulong mask = (ulong)-(long)bit;
power = (power & ~mask) | (multiplied & mask); power = (power & ~mask) | (multiplied & mask);
@@ -340,7 +380,7 @@ public static partial class ULongExtensions
break; break;
} }
baseValue = MontgomeryMultiply(baseValue, baseValue, modulus, nPrime); baseValue = MontgomeryMultiplyGpu(baseValue, baseValue, modulus, nPrime);
} }
return power; return power;
@@ -417,7 +457,6 @@ public static partial class ULongExtensions
ulong result = 0UL; ulong result = 0UL;
GpuKernelLease lease = GpuKernelPool.GetKernel(useGpuOrder: true); GpuKernelLease lease = GpuKernelPool.GetKernel(useGpuOrder: true);
var execution = lease.EnterExecutionScope();
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = lease.Accelerator;
// Keep this commented out. It should never happen in production code. // Keep this commented out. It should never happen in production code.
@@ -444,7 +483,6 @@ public static partial class ULongExtensions
resultBuffer.Dispose(); resultBuffer.Dispose();
exponentBuffer.Dispose(); exponentBuffer.Dispose();
execution.Dispose();
lease.Dispose(); lease.Dispose();
// Keep this commented. We don't want to catch any exceptions. All should crash the scanner. // Keep this commented. We don't want to catch any exceptions. All should crash the scanner.
// catch (AcceleratorException) // catch (AcceleratorException)
+4 -4
View File
@@ -553,7 +553,7 @@ public static partial class ULongExtensions
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong MontgomeryMultiply(this ulong a, ulong b, ulong modulus, ulong nPrime) public static ulong MontgomeryMultiplyCpu(this ulong a, ulong b, ulong modulus, ulong nPrime)
{ {
ulong tLow = unchecked(a * b); ulong tLow = unchecked(a * b);
ulong m = unchecked(tLow * nPrime); ulong m = unchecked(tLow * nPrime);
@@ -581,7 +581,7 @@ public static partial class ULongExtensions
{ {
if ((remainingExponent & 1UL) != 0UL) if ((remainingExponent & 1UL) != 0UL)
{ {
result = result.MontgomeryMultiply(baseVal, modulus, nPrime); result = result.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
} }
remainingExponent >>= 1; remainingExponent >>= 1;
@@ -590,10 +590,10 @@ public static partial class ULongExtensions
break; break;
} }
baseVal = baseVal.MontgomeryMultiply(baseVal, modulus, nPrime); baseVal = baseVal.MontgomeryMultiplyCpu(baseVal, modulus, nPrime);
} }
return keepMontgomery ? result : result.MontgomeryMultiply(1UL, modulus, nPrime); return keepMontgomery ? result : result.MontgomeryMultiplyCpu(1UL, modulus, nPrime);
} }
+2 -2
View File
@@ -131,12 +131,12 @@ The plan incorporates the updated divisor-class heuristics (Groups A/B, wheels,
* [done] The CPU heuristic now operates as a pure trial-division sweep without maintaining per-call summaries, relying on `PrepareHeuristicDivisor` only when downstream consumers request Montgomery data. * [done] The CPU heuristic now operates as a pure trial-division sweep without maintaining per-call summaries, relying on `PrepareHeuristicDivisor` only when downstream consumers request Montgomery data.
* [done] Introduced the temporary `UseHeuristicGroupBTrialDivision` gate so current builds execute Group A locally and then fall back to `Open.Numeric.Primes.Prime.Numbers.IsPrime`, keeping the full Group B implementation available for reactivation. * [done] Introduced the temporary `UseHeuristicGroupBTrialDivision` gate so current builds execute Group A locally and then fall back to `Open.Numeric.Primes.Prime.Numbers.IsPrime`, keeping the full Group B implementation available for reactivation.
7. [done] **Implement `IsPrimeGpu`** 7. [done] **Implement `IsPrimeGpu`**
* [done] Mirror CPU structure while batching divisor checks on the accelerator via `HeuristicTrialDivisionGpu`, drawing divisors from `HeuristicPrimeSieves`, reusing caller-provided `sqrtLimit`/`nMod10` inputs, and falling back to CPU when GPUs are disabled. * [done] Mirror CPU structure while batching divisor checks on the accelerator via `HeuristicTrialDivisionGpu`, drawing divisors from `HeuristicPrimeSieves`, reusing caller-provided `maxDivisorSquare`/`nMod10` inputs, and falling back to CPU when GPUs are disabled.
* [done] Added `PrimeTesterKernels.HeuristicTrialDivisionKernel` so GPU batches respect Group A/B ordering while emitting hit flags for early exits. * [done] Added `PrimeTesterKernels.HeuristicTrialDivisionKernel` so GPU batches respect Group A/B ordering while emitting hit flags for early exits.
* [done] Kept GPU divisibility in standard modular arithmetic while reserving Montgomery transforms for CPU confirmation logic. * [done] Kept GPU divisibility in standard modular arithmetic while reserving Montgomery transforms for CPU confirmation logic.
* [done] When the temporary Group B gate is disabled, delegate GPU calls to the CPU fallback so Group A coverage remains in place while the accelerator path stays available for future re-enablement. * [done] When the temporary Group B gate is disabled, delegate GPU calls to the CPU fallback so Group A coverage remains in place while the accelerator path stays available for future re-enablement.
8. [done] **Refactor by-divisor CPU/GPU testers to reuse heuristics** 8. [done] **Refactor by-divisor CPU/GPU testers to reuse heuristics**
* [done] Drove `MersenneNumberDivisorByDivisorCpuTester.CheckDivisors` from `HeuristicPrimeTester.CreateMersenneDivisorEnumerator`, eliminating the bespoke residue loop while preserving the existing status counters. * [done] Drove `MersenneNumberDivisorByDivisorCpuTester.CheckDivisors` from `HeuristicPrimeTester.CreateMersenneDivisorEnumerator`, eliminating the bespoke residue loop and retiring the legacy status counter.
* [done] Streamed GPU batches from the same enumerator and Montgomery preparation data, removing the filtered-divisor scratch arrays while continuing to feed the existing kernel and hit bookkeeping. * [done] Streamed GPU batches from the same enumerator and Montgomery preparation data, removing the filtered-divisor scratch arrays while continuing to feed the existing kernel and hit bookkeeping.
9. [done] **Route legacy prime checks through heuristics** 9. [done] **Route legacy prime checks through heuristics**
* [done] Introduce `HeuristicPrimeTester` so heuristic CPU/GPU checks live alongside the legacy `PrimeTester` implementations while callers opt in explicitly. * [done] Introduce `HeuristicPrimeTester` so heuristic CPU/GPU checks live alongside the legacy `PrimeTester` implementations while callers opt in explicitly.
-1
View File
@@ -41,7 +41,6 @@ Montgomery Primitives
Cycle-Aware Modular Arithmetic Cycle-Aware Modular Arithmetic
Status & Progress Reporting Status & Progress Reporting
- PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs:296: Replace this modulo with the ring-buffer style counter (subtract loop) used in the fast CLI status benchmarks so we avoid `%` in this hot loop while still wrapping progress correctly.
Cycle Remainder Steppers Cycle Remainder Steppers
- PerfectNumbers.Core/CycleRemainderStepper.cs:28: Inline this reset at the call sites so the hot loops reuse struct reinitialization measured fastest in MersenneDivisorCycleLengthGpuBenchmarks, avoiding the extra method call when scanners need to restart stepping. - PerfectNumbers.Core/CycleRemainderStepper.cs:28: Inline this reset at the call sites so the hot loops reuse struct reinitialization measured fastest in MersenneDivisorCycleLengthGpuBenchmarks, avoiding the extra method call when scanners need to restart stepping.