Compare commits

..
Author SHA1 Message Date
mlemanczyk 497b1fcf89 Kernels replaced with pre-compiled actions for better performance 2025-11-20 03:00:11 +01:00
mlemanczyk df04ba601c Various small optimizations and cleanups. Methods made static, where possible. Duplicated & slow PrimeTester.BinaryGcd implementation removed. Optimized version was used instead. 2025-11-20 01:43:29 +01:00
mlemanczyk 9939c5d562 Divisor will now be saved to CSV file 2025-11-19 22:14:28 +01:00
mlemanczyk 54fdff0a4e GPU calculations frequency adjusted 2025-11-19 21:38:07 +01:00
mlemanczyk 5cac51479e GPU memory allocations protected with lock to prevent shared memory access issues. GPU usage limited for the best performance. 2025-11-19 16:49:01 +01:00
mlemanczyk 02d16bf05c Tiny cleanup 2025-11-18 06:02:43 +01:00
mlemanczyk a5696ca17f App crash fix related to unassigned value 2025-11-18 06:02:22 +01:00
mlemanczyk 56be12ab95 Cleanup & compilation fixes. 2025-11-18 04:45:18 +01:00
mlemanczyk bc51962f9d Cleanup 2025-11-18 04:37:11 +01:00
mlemanczyk 801b1be3e3 Cleanup 2025-11-18 04:22:39 +01:00
mlemanczyk 66ca969929 Naming improvements and small cleanup 2025-11-18 03:43:52 +01:00
mlemanczyk bf7c4bab39 Accelerator buffers integrated 2025-11-18 01:13:12 +01:00
mlemanczyk 342eff2726 GpuKernelPool.GetOrAddKernels removed in --mersenne=bydivisor mode. PartialFactorResult.WithAdditionalPrime will now modify the current instance instead of creating new copy for better performance. SmallPrimeFactorViews renamed to SmallPrimeFactorGpuTables for consistency with other static tables. 2025-11-17 23:33:59 +01:00
mlemanczyk 2fc74cf98d Cleanup 2025-11-17 20:12:00 +01:00
mlemanczyk f630bfb598 HeuristicCombinedPrimeTesterAccelerator integrated with PrimeOrderCalculatorAccelerator. 2025-11-17 17:38:23 +01:00
mlemanczyk 183b147135 Heuristic tables split into 2 groups 2025-11-17 16:04:27 +01:00
mlemanczyk c413ee1162 Revert "Stream handling simplified to avoid dictionary lookups in favor of array lookups with known indexes."
This reverts commit a84445ef2a.
2025-11-16 17:14:48 +01:00
mlemanczyk a84445ef2a Stream handling simplified to avoid dictionary lookups in favor of array lookups with known indexes. 2025-11-16 15:01:51 +01:00
mlemanczyk cd7636ddbe Manual accelerator stream creations replaced with pool 2025-11-15 13:45:24 +01:00
mlemanczyk 0540411cad Accelerator index added for dictionary-less use-cases 2025-11-15 02:21:45 +01:00
mlemanczyk 6cb63b6e73 Internal accelerator array removed 2025-11-14 12:03:31 +01:00
mlemanczyk 42447c4e43 BinaryGcd switched to the best performing variant according to benchmarks. 2025-11-14 11:43:16 +01:00
mlemanczyk be435b7760 Kernels made part of the accelerators for better performance. 2025-11-13 18:52:46 +01:00
mlemanczyk 0d2a88c45f GpuPrimeWorkLimiter.Acquire() / .Release() removed to save time on the hot-path with billions of calls. We'll rely on AcceleratorStreamPool limits and synchronization. 2025-11-13 17:52:13 +01:00
mlemanczyk 1552e10ff0 VS Code settings updated. 2025-11-13 15:42:09 +01:00
mlemanczyk c53aa517f7 GpuScratchBuffer / -Pool naively integrated with PriemOrderCalculatorAccelerator (formerly Pow2MontgomeryAccelerator). FactorEntry replaced with 2 arrays - unused field removed. Small cleanup. 2025-11-13 14:41:27 +01:00
mlemanczyk 43863d03fb Performance improvements 2025-11-13 00:51:32 +01:00
mlemanczyk 5bc65464d0 Initial PollardRho implementation for GPU. Added pool for accelerator streams shared across the threads. GPU processing performance and stabilization improvements. GpuUInt128.BinaryGcd switched to the version which outperformed other in benchmarks. 2025-11-12 21:18:30 +01:00
mlemanczyk 735bfa1557 GPU accelerator recycled for better performance. 2025-11-12 07:39:52 +01:00
mlemanczyk fdc2da7dae Accelerator leak fixed. 2025-11-11 16:40:44 +01:00
mlemanczyk cf5f78c2ef PrimeTesterByLastDigitAccelerator integrated into Pow2MontgomeryGpuCalculator for better performance. 2025-11-11 13:36:21 +01:00
mlemanczyk 0de9871529 Functionality and classes separated. Kernel initialization made dependent on a flag. Cleanup. 2025-11-10 18:35:50 +01:00
mlemanczyk 45dfa3b724 Console logging extended with kernel warmup progress 2025-11-10 18:18:12 +01:00
mlemanczyk fa89958736 Stabilization improvements. Stream will be released when releasing GPU lease to prevent application crashes. 2025-11-10 14:30:30 +01:00
Marek Lemańczyk 2726c369f6 Merge pull request #256 from mlemanczyk/codex/optimize-evenperfectbitscanner-for-static-arrays
Adopt upstream GPU scratch isolation fixes
2025-11-09 21:53:50 +01:00
mlemanczyk d0b1639a5d GPU stabilization improvements to prevent calculation crashes 2025-11-09 21:52:16 +01:00
mlemanczyk a338bb0d0b Merge branch 'codex/optimize-evenperfectbitscanner-for-static-arrays' of https://github.com/mlemanczyk/even-perfect-numbers-scanner into codex/optimize-evenperfectbitscanner-for-static-arrays 2025-11-07 15:39:11 +01:00
mlemanczyk 8e779c5c9e GpuKernelLease removed 2025-11-07 15:36:45 +01:00
mlemanczyk da3093a9ba GpuKernelLease removed 2025-11-07 15:18:17 +01:00
mlemanczyk 0410fd2c91 PooledContext removal - continuation. GpuKernelLease prepared for removal. 2025-11-07 14:13:11 +01:00
mlemanczyk f4e365194d GpuContextPool removed, because we share context and accelerator between threads, using streams to separate. 2025-11-07 12:52:20 +01:00
mlemanczyk 7c035239b4 Cleanup. Classes & structs extracted to their own files. 2025-11-07 10:34:31 +01:00
mlemanczyk 119690086a ConcurrentDictionary removed. Kernel made dependent on the shared GPU context. 2025-11-07 10:16:33 +01:00
mlemanczyk 9537addf38 ArrayPool<T> replaced with ThreadStaticPools 2025-11-07 00:46:16 +01:00
mlemanczyk c004e140db Ignore words extended 2025-11-06 18:54:18 +01:00
mlemanczyk de2a78b27b Streams added to all kernels and GPU operations to separate threads from each other. 2025-11-06 18:53:55 +01:00
mlemanczyk 64d615c402 Various optimizations to separate non-static data between threads and resolve read/write access issue. 2025-11-04 20:45:56 +01:00
Marek Lemańczyk 5a17fa64d5 Restore per-lease kernel containers 2025-11-04 00:46:59 +01:00
Marek Lemańczyk dbde6337ea Tie GPU scratch buffers to context leases 2025-11-04 00:26:38 +01:00
Marek Lemańczyk 4c4d4243fb Merge remote-tracking branch 'github/codex/optimize-evenperfectbitscanner-for-static-arrays-khd9w8' into work
# Conflicts:
#	PerfectNumbers.Core/Gpu/GpuContextPool.cs
#	PerfectNumbers.Core/Gpu/GpuKernelLease.cs
#	PerfectNumbers.Core/Gpu/GpuKernelPool.cs
#	PerfectNumbers.Core/Gpu/GpuStaticTableInitializer.cs
#	PerfectNumbers.Core/Gpu/Kernels/PrimeOrderSpecialMaxKernels.cs
#	PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs
#	PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs
#	PerfectNumbers.Core/PrimeTester.cs
2025-11-03 23:59:12 +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
135 changed files with 6658 additions and 3826 deletions

No files matched your search

+3 -1
View File
@@ -9,5 +9,7 @@
"Mersenne", "Mersenne",
"primality", "primality",
"suff" "suff"
] ],
"geminicodeassist.project": "evenperfectbitscanner",
"todo-tree.tree.showCountsInTree": true
} }
@@ -1,4 +1,3 @@
using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -32,7 +32,7 @@ public class GpuUInt128Montgomery64Benchmarks
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public ulong ExtensionMontgomeryMultiply() public ulong ExtensionMontgomeryMultiply()
{ {
return Input.Left.MontgomeryMultiply(Input.Right, Input.Modulus, Input.NPrime); return Input.Left.MontgomeryMultiplyCpu(Input.Right, Input.Modulus, Input.NPrime);
} }
/// <summary> /// <summary>
@@ -1,6 +1,5 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using PerfectNumbers.Core;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
namespace EvenPerfectBitScanner.Benchmarks; namespace EvenPerfectBitScanner.Benchmarks;
@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using PerfectNumbers.Core; using PerfectNumbers.Core;
@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,5 +1,3 @@
using System;
namespace EvenPerfectBitScanner.Benchmarks; namespace EvenPerfectBitScanner.Benchmarks;
public enum ModNumericType public enum ModNumericType
@@ -1,4 +1,3 @@
using System;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,12 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
namespace EvenPerfectBitScanner.Benchmarks; namespace EvenPerfectBitScanner.Benchmarks;
@@ -1,4 +1,3 @@
using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -101,7 +100,7 @@ public class Pow2ModBenchmarks
for (int i = 0; i < SampleCount; i++) for (int i = 0; i < SampleCount; i++)
{ {
checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false); checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertGpu(divisors[i]);
} }
return checksum; return checksum;
@@ -640,7 +639,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 +735,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 +771,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 +816,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 +834,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 +851,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)
@@ -77,7 +77,7 @@ public class Pow2MontgomeryModBenchmarks
for (int i = 0; i < SampleCount; i++) for (int i = 0; i < SampleCount; i++)
{ {
checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false); checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertGpu(divisors[i]);
} }
return checksum; return checksum;
@@ -262,7 +262,7 @@ public class Pow2MontgomeryModBenchmarks
ulong nPrime = ComputeMontgomeryNPrime(modulus); ulong nPrime = ComputeMontgomeryNPrime(modulus);
ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus); ulong montgomeryOne = ComputeMontgomeryResidue(1UL, modulus);
ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, modulus); ulong montgomeryTwo = ComputeMontgomeryResidue(2UL, modulus);
ulong montgomeryTwoSquared = ULongExtensions.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,4 +1,3 @@
using System;
using PerfectNumbers.Core; using PerfectNumbers.Core;
namespace EvenPerfectBitScanner.Benchmarks; namespace EvenPerfectBitScanner.Benchmarks;
@@ -1,4 +1,3 @@
using System;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using PerfectNumbers.Core; using PerfectNumbers.Core;
@@ -34,7 +33,7 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks
for (int i = 0; i < cases.Length; i++) for (int i = 0; i < cases.Length; i++)
{ {
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i]; ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
checksum ^= current.Exponent.Pow2MontgomeryModWindowedGpu(current.Divisor, keepMontgomery: false); checksum ^= current.Exponent.Pow2MontgomeryModWindowedConvertGpu(current.Divisor);
} }
return checksum; return checksum;
@@ -52,7 +51,7 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks
for (int i = 0; i < cases.Length; i++) for (int i = 0; i < cases.Length; i++)
{ {
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i]; ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
checksum ^= current.Exponent.Pow2MontgomeryModWindowedGpu(current.Divisor, keepMontgomery: true); checksum ^= current.Exponent.Pow2MontgomeryModWindowedKeepGpu(current.Divisor);
} }
return checksum; return checksum;
@@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using System.Threading;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using Open.Numeric.Primes; using Open.Numeric.Primes;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace EvenPerfectBitScanner.Benchmarks; namespace EvenPerfectBitScanner.Benchmarks;
@@ -12,8 +10,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 +19,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 +26,23 @@ public class PrimeTesterBenchmarks
return; return;
} }
CancellationToken cancellationToken = CancellationToken.None; PrimeOrderCalculatorAccelerator.WarmUp();
_cpuTester.IsPrimeCpu(_candidates[0], cancellationToken); // HeuristicGroupABPrimeTesterAccelerator.WarmUp();
_gpuTester.IsPrimeGpu(_candidates[0]); _ = HeuristicPrimeTester.IsPrimeCpu(_candidates[0]);
_ = HeuristicPrimeTester.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 (HeuristicPrimeTester.IsPrimeCpu(values[i]))
{ {
primeCount++; primeCount++;
} }
@@ -59,14 +54,13 @@ 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++)
{ {
if (tester.IsPrimeGpu(values[i])) if (HeuristicPrimeTester.IsPrimeGpu(values[i]))
{ {
primeCount++; primeCount++;
} }
@@ -75,18 +69,49 @@ public class PrimeTesterBenchmarks
return primeCount; return primeCount;
} }
// This benchmark is very slow. No point in comparing it. [Benchmark]
// [Benchmark] public int HeuristicCombinedGpu()
{
ulong[] values = _candidates;
int primeCount = 0;
for (int i = 0; i < values.Length; i++)
{
if (HeuristicCombinedPrimeTester.IsPrimeGpu(values[i]))
{
primeCount++;
}
}
return primeCount;
}
[Benchmark]
public int HeuristicCombinedCpu()
{
ulong[] values = _candidates;
int primeCount = 0;
for (int i = 0; i < values.Length; i++)
{
if (HeuristicCombinedPrimeTester.IsPrimeCpu(values[i]))
{
primeCount++;
}
}
return primeCount;
}
[Benchmark]
public int NonHeuristicGpu() public int NonHeuristicGpu()
{ {
// PrimeTester tester = _gpuTester;
ulong[] values = _candidates; ulong[] values = _candidates;
CancellationToken cancellationToken = CancellationToken.None;
int primeCount = 0; int primeCount = 0;
for (int i = 0; i < values.Length; i++) for (int i = 0; i < values.Length; i++)
{ {
if (PrimeTester.Exclusive.IsPrimeGpu(values[i], cancellationToken)) if (PrimeTester.IsPrimeGpu(values[i]))
{ {
primeCount++; primeCount++;
} }
@@ -95,17 +120,15 @@ public class PrimeTesterBenchmarks
return primeCount; return primeCount;
} }
[Benchmark] [Benchmark(Baseline = true)]
public int NonHeuristicCpu() public int NonHeuristicCpu()
{ {
// PrimeTester tester = _gpuTester;
ulong[] values = _candidates; ulong[] values = _candidates;
CancellationToken cancellationToken = CancellationToken.None;
int primeCount = 0; int primeCount = 0;
for (int i = 0; i < values.Length; i++) for (int i = 0; i < values.Length; i++)
{ {
if (PrimeTester.IsPrimeCpu(values[i], cancellationToken)) if (PrimeTester.IsPrime(values[i]))
{ {
primeCount++; primeCount++;
} }
@@ -114,7 +137,7 @@ public class PrimeTesterBenchmarks
return primeCount; return primeCount;
} }
[Benchmark] // [Benchmark]
public int OpenNumericPrimes() public int OpenNumericPrimes()
{ {
ulong[] values = _candidates; ulong[] values = _candidates;
@@ -19,7 +19,7 @@ internal static class Program
"/help" "/help"
}; };
private const string DefaultHeader = "p,searchedMersenne,detailedCheck,passedAllTests"; private const string DefaultHeader = "p,divisor,searchedMersenne,detailedCheck,passedAllTests";
private static int Main(string[] args) private static int Main(string[] args)
{ {
+1 -39
View File
@@ -3,13 +3,9 @@ using Xunit;
using System.Reflection; using System.Reflection;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using EvenPerfectBitScanner;
using EvenPerfectBitScanner.Candidates; using EvenPerfectBitScanner.Candidates;
using EvenPerfectBitScanner.Candidates.Transforms; using EvenPerfectBitScanner.Candidates.Transforms;
using System.Numerics; using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
namespace EvenPerfectBitScanner.Tests; namespace EvenPerfectBitScanner.Tests;
@@ -55,7 +51,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]
@@ -102,13 +98,11 @@ public class ProgramTests
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!; var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!; var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
testerField.SetValue(null, new MersenneNumberDivisorGpuTester()); testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
candidatesField.SetValue(null, new (ulong, uint)[] { (7UL, 3U), (23UL, 11U) }); candidatesField.SetValue(null, new (ulong, uint)[] { (7UL, 3U), (23UL, 11U) });
forceCpuProp!.SetValue(null, true);
try try
{ {
@@ -122,7 +116,6 @@ public class ProgramTests
candidatesField.SetValue(null, null); candidatesField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -135,13 +128,11 @@ public class ProgramTests
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!; var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!; var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
testerField.SetValue(null, new MersenneNumberDivisorGpuTester()); testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
candidatesField.SetValue(null, Array.Empty<(ulong, uint)>()); candidatesField.SetValue(null, Array.Empty<(ulong, uint)>());
forceCpuProp!.SetValue(null, true);
try try
{ {
@@ -155,7 +146,6 @@ public class ProgramTests
candidatesField.SetValue(null, null); candidatesField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -168,12 +158,10 @@ public class ProgramTests
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!; var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
testerField.SetValue(null, new MersenneNumberDivisorGpuTester()); testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, true);
try try
{ {
@@ -186,7 +174,6 @@ public class ProgramTests
testerField.SetValue(null, null); testerField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -199,12 +186,10 @@ public class ProgramTests
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!; var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
testerField.SetValue(null, new MersenneNumberDivisorGpuTester()); testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -221,7 +206,6 @@ public class ProgramTests
testerField.SetValue(null, null); testerField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -233,12 +217,10 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(useIncremental: true, useResidue: true, maxK: 1_000UL), trackAllValues: true)); mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(useIncremental: true, useResidue: true, maxK: 1_000UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -255,7 +237,6 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -267,7 +248,6 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester( mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useIncremental: true, useIncremental: true,
@@ -275,7 +255,6 @@ public class ProgramTests
maxK: 1_024UL), trackAllValues: true)); maxK: 1_024UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -292,7 +271,6 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -305,7 +283,6 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester( mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useIncremental: true, useIncremental: true,
@@ -313,7 +290,6 @@ public class ProgramTests
maxK: 5_000_000UL), trackAllValues: true)); maxK: 5_000_000UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -330,7 +306,6 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -342,7 +317,6 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester( mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useIncremental: true, useIncremental: true,
@@ -350,7 +324,6 @@ public class ProgramTests
maxK: 5_000_000UL), trackAllValues: true)); maxK: 5_000_000UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -367,7 +340,6 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -380,7 +352,6 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester( mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useIncremental: true, useIncremental: true,
@@ -390,7 +361,6 @@ public class ProgramTests
maxK: 1_024UL), trackAllValues: true)); maxK: 1_024UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
forceCpuProp!.SetValue(null, true);
try try
{ {
@@ -402,7 +372,6 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -417,7 +386,6 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester( mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useIncremental: false, useIncremental: false,
@@ -428,7 +396,6 @@ public class ProgramTests
maxK: configuration.ResidueMaxK), trackAllValues: true)); maxK: configuration.ResidueMaxK), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -440,9 +407,7 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
GpuContextPool.DisposeAll();
} }
} }
@@ -506,12 +471,10 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var compositeField = typeof(Program).GetField("_lastCompositeP", BindingFlags.NonPublic | BindingFlags.Static)!; var compositeField = typeof(Program).GetField("_lastCompositeP", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(useIncremental: true, useResidue: false, maxK: 1_000UL), trackAllValues: true)); mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(useIncremental: true, useResidue: false, maxK: 1_000UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, true);
try try
{ {
@@ -532,7 +495,6 @@ public class ProgramTests
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
compositeField.SetValue(null, false); compositeField.SetValue(null, false);
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -1,6 +1,3 @@
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
@@ -1,15 +1,13 @@
using System.Buffers.Text; using System.Buffers.Text;
using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading;
using PerfectNumbers.Core; using PerfectNumbers.Core;
namespace EvenPerfectBitScanner; namespace EvenPerfectBitScanner;
internal static class CalculationResultHandler internal static class CalculationResultHandler
{ {
private const string ResultsHeader = "p,searchedMersenne,detailedCheck,passedAllTests"; private const string ResultsHeader = "p,divisor,searchedMersenne,detailedCheck,passedAllTests";
private const string PrimeFoundSuffix = " (FOUND VALID CANDIDATES)"; private const string PrimeFoundSuffix = " (FOUND VALID CANDIDATES)";
internal const int DefaultWriteBatchSize = 100; internal const int DefaultWriteBatchSize = 100;
@@ -95,19 +93,19 @@ internal static class CalculationResultHandler
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void HandleResult(ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite) internal static void HandleResult(ulong currentP, ulong divisor, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite)
{ {
// Program flow ensures Initialize(...) and InitializeOutputBuffer() ran before this call. Do not add initialization guards here. // Program flow ensures Initialize(...) and InitializeOutputBuffer() ran before this call. Do not add initialization guards here.
Span<byte> stackBuffer = stackalloc byte[128]; Span<byte> stackBuffer = stackalloc byte[128];
byte[]? rentedBuffer = null; byte[]? rentedBuffer = null;
Span<byte> utf8Span = stackBuffer; Span<byte> utf8Span = stackBuffer;
int byteCount = TryFormatResultLine(utf8Span, currentP, searchedMersenne, detailedCheck, passedAllTests); int byteCount = TryFormatResultLine(utf8Span, currentP, divisor, searchedMersenne, detailedCheck, passedAllTests);
if (byteCount < 0) if (byteCount < 0)
{ {
rentedBuffer = ThreadStaticPools.BytePool.Rent(256); rentedBuffer = ThreadStaticPools.BytePool.Rent(256);
utf8Span = rentedBuffer; utf8Span = rentedBuffer;
byteCount = TryFormatResultLine(utf8Span, currentP, searchedMersenne, detailedCheck, passedAllTests); byteCount = TryFormatResultLine(utf8Span, currentP, divisor, searchedMersenne, detailedCheck, passedAllTests);
if (byteCount < 0) if (byteCount < 0)
{ {
ThreadStaticPools.BytePool.Return(rentedBuffer); ThreadStaticPools.BytePool.Return(rentedBuffer);
@@ -231,7 +229,7 @@ internal static class CalculationResultHandler
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int TryFormatResultLine(Span<byte> destination, ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests) private static int TryFormatResultLine(Span<byte> destination, ulong currentP, ulong divisor, bool searchedMersenne, bool detailedCheck, bool passedAllTests)
{ {
int offset = 0; int offset = 0;
@@ -248,6 +246,19 @@ internal static class CalculationResultHandler
destination[offset++] = (byte)','; destination[offset++] = (byte)',';
if (!Utf8Formatter.TryFormat(divisor, destination[offset..], out written))
{
return -1;
}
offset += written;
if (offset >= destination.Length)
{
return -1;
}
destination[offset++] = (byte)',';
if (!Utf8Formatter.TryFormat(searchedMersenne, destination[offset..], out written)) if (!Utf8Formatter.TryFormat(searchedMersenne, destination[offset..], out written))
{ {
return -1; return -1;
@@ -1,5 +1,4 @@
using System.Globalization; using System.Globalization;
using System.IO;
namespace EvenPerfectBitScanner.Candidates; namespace EvenPerfectBitScanner.Candidates;
@@ -1,5 +1,4 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using Open.Numeric.Primes; using Open.Numeric.Primes;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using EvenPerfectBitScanner.Candidates.Transforms; using EvenPerfectBitScanner.Candidates.Transforms;
@@ -1,6 +1,4 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using EvenPerfectBitScanner.Candidates;
using Open.Numeric.Primes; using Open.Numeric.Primes;
namespace EvenPerfectBitScanner.Candidates.Transforms; namespace EvenPerfectBitScanner.Candidates.Transforms;
@@ -1,6 +1,4 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using EvenPerfectBitScanner.Candidates;
namespace EvenPerfectBitScanner.Candidates.Transforms; namespace EvenPerfectBitScanner.Candidates.Transforms;
@@ -1,5 +1,4 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
namespace EvenPerfectBitScanner.Candidates.Transforms; namespace EvenPerfectBitScanner.Candidates.Transforms;
+45 -36
View File
@@ -1,6 +1,4 @@
using System;
using System.Globalization; using System.Globalization;
using System.Numerics;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
@@ -202,14 +200,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 +237,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 +280,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 +290,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 +300,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 +318,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 +334,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 +364,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 +392,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 +409,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 +426,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;
} }
@@ -1,7 +1,6 @@
using Open.Collections; using Open.Collections;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using EvenPerfectBitScanner;
namespace EvenPerfectBitScanner.IO; namespace EvenPerfectBitScanner.IO;
@@ -14,7 +13,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 +21,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 +85,7 @@ internal static class CalculationResultsFile
} }
} }
} }
}
return candidates; return candidates;
} }
@@ -70,6 +100,7 @@ internal static class CalculationResultsFile
int first = 0; int first = 0;
int second = 0; int second = 0;
int third = 0; int third = 0;
int fourth = 0;
ulong parsedP = 0UL; ulong parsedP = 0UL;
ReadOnlySpan<char> detailedSpan = default; ReadOnlySpan<char> detailedSpan = default;
ReadOnlySpan<char> passedAllTestsSpan = default; ReadOnlySpan<char> passedAllTestsSpan = default;
@@ -109,8 +140,15 @@ internal static class CalculationResultsFile
continue; continue;
} }
detailedSpan = span[..third]; span = span[(third + 1)..];
passedAllTestsSpan = span[(third + 1)..]; fourth = span.IndexOf(',');
if (fourth < 0)
{
continue;
}
detailedSpan = span[..fourth];
passedAllTestsSpan = span[(fourth + 1)..];
if (Utf8CliParser.TryParseBoolean(detailedSpan, out detailed) && Utf8CliParser.TryParseBoolean(passedAllTestsSpan, out passedAllTests)) if (Utf8CliParser.TryParseBoolean(detailedSpan, out detailed) && Utf8CliParser.TryParseBoolean(passedAllTestsSpan, out passedAllTests))
{ {
+23 -28
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,24 @@ 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 = SharedGpuContext.Device.MaxNumThreads;
int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads);
UnboundedTaskScheduler.ConfigureThreadCount(threadCount); UnboundedTaskScheduler.ConfigureThreadCount(threadCount);
PrimeTester.GpuBatchSize = gpuPrimeBatch;
GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads);
Console.WriteLine("Warming up GPU kernels");
PrimeTester.WarmUpGpuKernels(1024);
// PrimeTester.WarmUpGpuKernels(gpuPrimeThreads >> 4);
Console.WriteLine("Starting up threads..."); Console.WriteLine("Starting up threads...");
_ = UnboundedTaskScheduler.Instance; _ = UnboundedTaskScheduler.Instance;
int blockSize = Math.Max(1, _cliArguments.BlockSize); int blockSize = Math.Max(1, _cliArguments.BlockSize);
int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads);
int gpuPrimeBatch = Math.Max(1, _cliArguments.GpuPrimeBatch);
GpuKernelType kernelType = _cliArguments.KernelType; GpuKernelType kernelType = _cliArguments.KernelType;
bool useBitTransform = _cliArguments.UseBitTransform; bool useBitTransform = _cliArguments.UseBitTransform;
bool useOrder = _cliArguments.UseOrder; bool useOrder = _cliArguments.UseOrder;
@@ -104,14 +112,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 +120,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 +175,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 +261,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))
@@ -427,7 +422,7 @@ internal static class Program
_byDivisorStartPrime, _byDivisorStartPrime,
static () => _lastCompositeP = true, static () => _lastCompositeP = true,
static () => _lastCompositeP = false, static () => _lastCompositeP = false,
static (candidate, searchedMersenne, detailedCheck, passedAllTests) => CalculationResultHandler.HandleResult(candidate, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP), static (candidate, searchedMersenne, detailedCheck, passedAllTests, divisor) => CalculationResultHandler.HandleResult(candidate, divisor, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP),
threadCount, threadCount,
byDivisorPrimeRange); byDivisorPrimeRange);
@@ -488,7 +483,7 @@ internal static class Program
{ {
p = buffer[j]; p = buffer[j];
passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck); passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck);
CalculationResultHandler.HandleResult(p, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP); CalculationResultHandler.HandleResult(p, 0UL, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP);
} }
continue; continue;
@@ -511,7 +506,7 @@ internal static class Program
} }
passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck); passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck);
CalculationResultHandler.HandleResult(p, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP); CalculationResultHandler.HandleResult(p, 0UL, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP);
if (p == maxP) if (p == maxP)
{ {
@@ -631,9 +626,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))) : HeuristicCombinedPrimeTester.IsPrimeGpu(p)))
{ {
_lastCompositeP = true; _lastCompositeP = true;
return false; return false;
@@ -654,7 +649,7 @@ internal static class Program
if (_cliArguments.UseByDivisor) if (_cliArguments.UseByDivisor)
{ {
// Windowed pow2mod kernel handles by-divisor scans. // Windowed pow2mod kernel handles by-divisor scans.
return _byDivisorTester!.IsPrime(p, out detailedCheck); return _byDivisorTester!.IsPrime(p, out detailedCheck, out _);
} }
detailedCheck = MersenneTesters.Value!.IsMersennePrime(p); detailedCheck = MersenneTesters.Value!.IsMersennePrime(p);
-1
View File
@@ -1,4 +1,3 @@
using System;
using System.Buffers; using System.Buffers;
using System.Buffers.Text; using System.Buffers.Text;
using System.Text; using System.Text;
@@ -1,7 +1,3 @@
using FluentAssertions;
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu;
using Xunit; using Xunit;
namespace PerfectNumbers.Core.Tests.Gpu; namespace PerfectNumbers.Core.Tests.Gpu;
@@ -9,88 +5,88 @@ namespace PerfectNumbers.Core.Tests.Gpu;
[Collection("GpuNtt")] [Collection("GpuNtt")]
public class GpuContextPoolTests public class GpuContextPoolTests
{ {
[Fact] // [Fact]
[Trait("Category", "Fast")] // [Trait("Category", "Fast")]
public void RentPreferred_cpu_returns_cpu_accelerator() // public void RentPreferred_cpu_returns_cpu_accelerator()
{ // {
GpuContextPool.DisposeAll(); // GpuContextPool.DisposeAll();
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true); // GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
try // try
{ // {
lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU); // lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU);
} // }
finally // finally
{ // {
lease.Dispose(); // lease.Dispose();
} // }
GpuContextPool.DisposeAll(); // GpuContextPool.DisposeAll();
} // }
[Fact] // [Fact]
[Trait("Category", "Fast")] // [Trait("Category", "Fast")]
public void RentPreferred_reuses_cpu_context() // public void RentPreferred_reuses_cpu_context()
{ // {
GpuContextPool.DisposeAll(); // GpuContextPool.DisposeAll();
Context firstContext; // Context firstContext;
Accelerator firstAccelerator; // Accelerator firstAccelerator;
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true); // GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
try // try
{ // {
firstContext = lease.Context; // firstContext = lease.Context;
firstAccelerator = lease.Accelerator; // firstAccelerator = lease.Accelerator;
} // }
finally // finally
{ // {
lease.Dispose(); // lease.Dispose();
} // }
lease = GpuContextPool.RentPreferred(preferCpu: true); // lease = GpuContextPool.RentPreferred(preferCpu: true);
try // try
{ // {
lease.Context.Should().BeSameAs(firstContext); // lease.Context.Should().BeSameAs(firstContext);
lease.Accelerator.Should().BeSameAs(firstAccelerator); // lease.Accelerator.Should().BeSameAs(firstAccelerator);
} // }
finally // finally
{ // {
lease.Dispose(); // lease.Dispose();
} // }
GpuContextPool.DisposeAll(); // GpuContextPool.DisposeAll();
} // }
[Fact] // [Fact]
[Trait("Category", "Fast")] // [Trait("Category", "Fast")]
public void DisposeAll_clears_pooled_contexts() // public void DisposeAll_clears_pooled_contexts()
{ // {
GpuContextPool.DisposeAll(); // GpuContextPool.DisposeAll();
Context firstContext; // Context firstContext;
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true); // GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
try // try
{ // {
firstContext = lease.Context; // firstContext = lease.Context;
} // }
finally // finally
{ // {
lease.Dispose(); // lease.Dispose();
} // }
GpuContextPool.DisposeAll(); // GpuContextPool.DisposeAll();
lease = GpuContextPool.RentPreferred(preferCpu: true); // lease = GpuContextPool.RentPreferred(preferCpu: true);
try // try
{ // {
lease.Context.Should().NotBeSameAs(firstContext); // lease.Context.Should().NotBeSameAs(firstContext);
} // }
finally // finally
{ // {
lease.Dispose(); // lease.Dispose();
} // }
GpuContextPool.DisposeAll(); // GpuContextPool.DisposeAll();
} // }
} }
@@ -1,8 +1,8 @@
using System.Reflection; using System.Reflection;
using FluentAssertions;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit; using Xunit;
namespace PerfectNumbers.Core.Tests.Gpu; namespace PerfectNumbers.Core.Tests.Gpu;
@@ -114,14 +114,10 @@ public class GpuKernelCompilationTests
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModKernel", BindingFlags.NonPublic | BindingFlags.Static) typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModKernel not found."); ?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModKernel not found.");
private static readonly MethodInfo PrimeOrderGetPow2ModWideKernelMethod =
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModWideKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModWideKernel not found.");
private static readonly Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> MersenneDivisorCyclesKernel = private static readonly Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> MersenneDivisorCyclesKernel =
DivisorCycleKernels.GpuDivisorCycleKernel; DivisorCycleKernels.GpuDivisorCycleKernel;
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>> PrimeTesterSmallPrimeKernel = private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>> PrimeTesterSmallPrimeKernel =
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 +163,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)
{ {
@@ -325,56 +321,67 @@ public class GpuKernelCompilationTests
private static void CompilePrimeOrderPow2ModWideKernel(Accelerator accelerator) private static void CompilePrimeOrderPow2ModWideKernel(Accelerator accelerator)
{ {
_ = PrimeOrderGetPow2ModWideKernelMethod.Invoke(null, new object[] { accelerator }); var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>>(PrimeOrderGpuHeuristics.Pow2ModKernelWide);
_ = kernel;
} }
private static void CompileMersenneDivisorCyclesKernel(Accelerator accelerator) private static void CompileMersenneDivisorCyclesKernel(Accelerator accelerator)
{ {
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(MersenneDivisorCyclesKernel); var kernel = accelerator.LoadAutoGroupedStreamKernel(MersenneDivisorCyclesKernel);
_ = kernel; _ = kernel;
} }
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(PrimeTesterSmallPrimeKernel);
_ = kernel; _ = kernel;
} }
private static void CompilePrimeTesterSharesFactorKernel(Accelerator accelerator) private static void CompilePrimeTesterSharesFactorKernel(Accelerator accelerator)
{ {
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterSharesFactorKernel); var kernel = accelerator.LoadAutoGroupedStreamKernel(PrimeTesterSharesFactorKernel);
_ = kernel; _ = kernel;
} }
private static KernelType _kernelType = KernelType.Pow2ModKernelScan | KernelType.OrderKernelScan | KernelType.IncrementalKernelScan | KernelType.IncrementalOrderKernelScan | KernelType.Pow2ModOrderKernelScan;
private static void CompileGpuKernelPoolKernels(Accelerator accelerator) private static void CompileGpuKernelPoolKernels(Accelerator accelerator)
{ {
_ = accelerator;
var lease = GpuKernelPool.GetKernel(useGpuOrder: false); GpuPrimeWorkLimiter.Acquire();
int acceleratorIndex = AcceleratorPool.Shared.Rent();
AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex);
try try
{ {
_ = lease.OrderKernel; var kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, _kernelType);
_ = lease.IncrementalKernel; _ = kernels.Order;
_ = lease.Pow2ModKernel; _ = kernels.Incremental;
_ = lease.IncrementalOrderKernel; _ = kernels.Pow2Mod;
_ = lease.Pow2ModOrderKernel; _ = kernels.IncrementalOrder;
_ = kernels.Pow2ModOrder;
} }
finally finally
{ {
lease.Dispose(); stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
// GpuPrimeWorkLimiter.Release();
} }
var orderLease = GpuKernelPool.GetKernel(useGpuOrder: true); stream = AcceleratorStreamPool.Rent(acceleratorIndex);
GpuPrimeWorkLimiter.Acquire();
try try
{ {
_ = orderLease.OrderKernel; var kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, _kernelType);
_ = orderLease.IncrementalKernel; _ = kernels.Order;
_ = orderLease.Pow2ModKernel; _ = kernels.Incremental;
_ = orderLease.IncrementalOrderKernel; _ = kernels.Pow2Mod;
_ = orderLease.Pow2ModOrderKernel; _ = kernels.IncrementalOrder;
_ = kernels.Pow2ModOrder;
} }
finally finally
{ {
orderLease.Dispose(); stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
// GpuPrimeWorkLimiter.Release();
} }
} }
} }
@@ -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]
@@ -150,10 +150,8 @@ public class NttGpuMathTests
var modulus = new GpuUInt128(0UL, 17UL); var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL); var primitiveRoot = new GpuUInt128(0UL, 3UL);
GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent(); var accelerator = SharedGpuContext.CreateAccelerator();
try var stream = accelerator.CreateStream();
{
var accelerator = gpu.Accelerator;
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static); var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
method.Should().NotBeNull(); method.Should().NotBeNull();
@@ -168,8 +166,11 @@ public class NttGpuMathTests
int count = 7; // length 8 -> 7 twiddles int count = 7; // length 8 -> 7 twiddles
var forward = new GpuUInt128[count]; var forward = new GpuUInt128[count];
var inverse = new GpuUInt128[count]; var inverse = new GpuUInt128[count];
twiddlesMont.CopyToCPU(forward); twiddlesMont.CopyToCPU(stream, forward);
twiddlesInvMont.CopyToCPU(inverse); twiddlesInvMont.CopyToCPU(stream, inverse);
stream.Synchronize();
stream.Dispose();
accelerator.Dispose();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
@@ -177,11 +178,6 @@ public class NttGpuMathTests
prod.Should().Be(r2); prod.Should().Be(r2);
} }
} }
finally
{
gpu.Dispose();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -345,24 +341,15 @@ public class NttGpuMathTests
values[1] = new GpuUInt128(0UL, 2UL); values[1] = new GpuUInt128(0UL, 2UL);
var modulus = new GpuUInt128(0UL, 17UL); var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL); var primitiveRoot = new GpuUInt128(0UL, 3UL);
GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent(); Accelerator accelerator = SharedGpuContext.CreateAccelerator();
try var stream = accelerator.CreateStream();
{ var buffer = accelerator.Allocate1D<GpuUInt128>(values.Length);
var buffer = gpu.Accelerator.Allocate1D<GpuUInt128>(values.Length); buffer.View.CopyFromCPU(stream, ref values[0], values.Length);
try NttGpuMath.SquareDevice(accelerator, stream, buffer.View, modulus, primitiveRoot);
{ stream.Synchronize();
buffer.View.CopyFromCPU(ref values[0], values.Length); stream.Dispose();
NttGpuMath.SquareDevice(gpu.Accelerator, buffer.View, modulus, primitiveRoot);
}
finally
{
buffer.Dispose(); buffer.Dispose();
} accelerator.Dispose();
}
finally
{
gpu.Dispose();
}
// Verify that the square cache now holds entries for the accelerator. // Verify that the square cache now holds entries for the accelerator.
var field = typeof(NttGpuMath).GetField("SquareCache", BindingFlags.NonPublic | BindingFlags.Static)!; var field = typeof(NttGpuMath).GetField("SquareCache", BindingFlags.NonPublic | BindingFlags.Static)!;
@@ -370,8 +357,6 @@ public class NttGpuMathTests
int count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!; int count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
count.Should().BeGreaterThan(0); count.Should().BeGreaterThan(0);
GpuContextPool.DisposeAll();
count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!; count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
count.Should().Be(0); count.Should().Be(0);
} }
@@ -33,24 +33,21 @@ public class NttMontgomeryTests
NttGpuMath.Square(cpu, modulus, primitiveRoot); NttGpuMath.Square(cpu, modulus, primitiveRoot);
// Device execution // Device execution
GpuContextPool.GpuContextLease context = GpuContextPool.Rent(); var accelerator = SharedGpuContext.CreateAccelerator();
try var stream = accelerator.CreateStream();
{
var accelerator = context.Accelerator;
using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length); using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
buffer.View.CopyFromCPU(ref gpu[0], gpu.Length); buffer.View.CopyFromCPU(stream, ref gpu[0], gpu.Length);
NttGpuMath.SquareDevice(accelerator, buffer.View, modulus, primitiveRoot); NttGpuMath.SquareDevice(accelerator, stream, buffer.View, modulus, primitiveRoot);
buffer.View.CopyToCPU(ref gpu[0], gpu.Length); buffer.View.CopyToCPU(stream, ref gpu[0], gpu.Length);
stream.Synchronize();
stream.Dispose();
accelerator.Dispose();
for (int i = 0; i < cpu.Length; i++) for (int i = 0; i < cpu.Length; i++)
{ {
gpu[i].Should().Be(cpu[i]); gpu[i].Should().Be(cpu[i]);
} }
}
finally
{
context.Dispose();
}
} }
[Fact] [Fact]
@@ -1,5 +1,4 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core.Gpu;
using Xunit; using Xunit;
namespace PerfectNumbers.Core.Tests.Gpu; namespace PerfectNumbers.Core.Tests.Gpu;
@@ -9,41 +8,23 @@ public class PrimeTesterGpuTests
{ {
[Fact] [Fact]
public void IsPrimeGpu_accepts_known_primes() public void IsPrimeGpu_accepts_known_primes()
{
GpuContextPool.ForceCpu = false;
try
{ {
var tester = new PrimeTester(); var tester = new PrimeTester();
ulong[] primes = [31UL, 61UL, 89UL, 107UL, 127UL, 521UL]; ulong[] primes = [31UL, 61UL, 89UL, 107UL, 127UL, 521UL];
foreach (ulong prime in primes) foreach (ulong prime in primes)
{ {
tester.IsPrimeGpu(prime, CancellationToken.None).Should().BeTrue(); PrimeTester.IsPrimeGpu(prime).Should().BeTrue();
}
}
finally
{
GpuContextPool.ForceCpu = false;
GpuContextPool.DisposeAll();
} }
} }
[Fact] [Fact]
public void IsPrimeGpu_rejects_composites() public void IsPrimeGpu_rejects_composites()
{
GpuContextPool.ForceCpu = false;
try
{ {
var tester = new PrimeTester(); var tester = new PrimeTester();
ulong[] composites = [33UL, 39UL, 51UL, 77UL, 91UL]; ulong[] composites = [33UL, 39UL, 51UL, 77UL, 91UL];
foreach (ulong composite in composites) foreach (ulong composite in composites)
{ {
tester.IsPrimeGpu(composite, CancellationToken.None).Should().BeFalse(); PrimeTester.IsPrimeGpu(composite).Should().BeFalse();
}
}
finally
{
GpuContextPool.ForceCpu = false;
GpuContextPool.DisposeAll();
} }
} }
@@ -1,5 +1,4 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using Xunit; using Xunit;
namespace PerfectNumbers.Core.Tests.Gpu; namespace PerfectNumbers.Core.Tests.Gpu;
+18 -18
View File
@@ -7,26 +7,26 @@ namespace PerfectNumbers.Core.Tests;
[Collection("GpuNtt")] [Collection("GpuNtt")]
public class GpuKernelPoolTests public class GpuKernelPoolTests
{ {
[Fact] // [Fact]
[Trait("Category", "Fast")] // [Trait("Category", "Fast")]
public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls() // public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls()
{ // {
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true); // GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
try // try
{ // {
var accelerator = lease.Accelerator; // var accelerator = lease.Accelerator;
var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); // var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
var second = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); // var second = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
second.Equals(first).Should().BeTrue(); // second.Equals(first).Should().BeTrue();
second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1); // second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1);
} // }
finally // finally
{ // {
lease.Dispose(); // lease.Dispose();
} // }
} // }
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -17,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,235 @@
using FluentAssertions;
using Xunit;
namespace PerfectNumbers.Core.Tests;
public class HeuristicCombinedPrimeTesterTests
{
[Theory]
[Trait("Category", "Fast")]
[InlineData((byte)1)]
[InlineData((byte)7)]
[InlineData((byte)9)]
public void Enumerator_uses_one_a_one_b_schedule_by_default(byte lastDigit)
{
Span<HeuristicCombinedPrimeTester.HeuristicGroupBSequenceState> groupBState =
stackalloc HeuristicCombinedPrimeTester.HeuristicGroupBSequenceState[4];
var enumerator = HeuristicCombinedPrimeTester.CreateHeuristicDivisorEnumerator(500UL * 500UL, lastDigit, groupBState);
var results = new List<HeuristicCombinedPrimeTester.HeuristicDivisorCandidate>(32);
while (results.Count < 20 && enumerator.TryGetNext(out var candidate))
{
results.Add(candidate);
}
const int segmentsToCheck = 6;
results.Count.Should().BeGreaterThanOrEqualTo(4 + segmentsToCheck * 2);
results[0].Value.Should().Be(3UL);
results[0].Group.Should().Be(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupAConstant);
results[1].Value.Should().Be(7UL);
results[1].Group.Should().Be(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupAConstant);
results[2].Value.Should().Be(11UL);
results[2].Group.Should().Be(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupAConstant);
results[3].Value.Should().Be(13UL);
results[3].Group.Should().Be(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupAConstant);
int index = 4;
for (int segment = 0; segment < segmentsToCheck; segment++)
{
HeuristicCombinedPrimeTester.HeuristicDivisorCandidate groupACandidate = results[index++];
groupACandidate.Value.Should().BeGreaterThan(13UL);
(groupACandidate.Value % 10UL).Should().Be(3UL);
groupACandidate.Group.Should().NotBe(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupB);
HeuristicCombinedPrimeTester.HeuristicDivisorCandidate groupBCandidate = results[index++];
groupBCandidate.Value.Should().BeGreaterThan(13UL);
(groupBCandidate.Value % 10UL).Should().NotBe(3UL);
groupBCandidate.Group.Should().Be(HeuristicCombinedPrimeTester.HeuristicDivisorGroup.GroupB);
}
}
[Theory]
[Trait("Category", "Fast")]
[InlineData((byte)1)]
[InlineData((byte)7)]
[InlineData((byte)9)]
public void One_a_one_b_pattern_interleaves_group_a_and_group_b_values(byte lastDigit)
{
ReadOnlySpan<ulong> combined = HeuristicCombinedPrimeTester.GetCombinedDivisors(
lastDigit,
HeuristicCombinedPrimeTester.CombinedDivisorPattern.OneAOneB);
combined.Length.Should().BeGreaterThan(18);
combined[0].Should().Be(3UL);
combined[1].Should().Be(7UL);
combined[2].Should().Be(11UL);
combined[3].Should().Be(13UL);
int index = 4;
const int segmentsToCheck = 6;
for (int segment = 0; segment < segmentsToCheck; segment++)
{
ulong groupAValue = combined[index++];
groupAValue.Should().BeGreaterThan(13UL);
(groupAValue % 10UL).Should().Be(3UL);
ulong groupBValue = combined[index++];
groupBValue.Should().BeGreaterThan(13UL);
(groupBValue % 10UL).Should().NotBe(3UL);
}
}
[Theory]
[Trait("Category", "Fast")]
[InlineData((byte)1)]
[InlineData((byte)7)]
[InlineData((byte)9)]
public void Two_a_one_b_pattern_interleaves_two_group_a_values_before_group_b(byte lastDigit)
{
ReadOnlySpan<ulong> combined = HeuristicCombinedPrimeTester.GetCombinedDivisors(
lastDigit,
HeuristicCombinedPrimeTester.CombinedDivisorPattern.TwoAOneB);
combined.Length.Should().BeGreaterThan(20);
combined[0].Should().Be(3UL);
combined[1].Should().Be(7UL);
combined[2].Should().Be(11UL);
combined[3].Should().Be(13UL);
int index = 4;
const int segmentsToCheck = 5;
for (int segment = 0; segment < segmentsToCheck; segment++)
{
for (int i = 0; i < 2; i++)
{
ulong groupAValue = combined[index++];
groupAValue.Should().BeGreaterThan(13UL);
(groupAValue % 10UL).Should().Be(3UL);
}
ulong groupBValue = combined[index++];
groupBValue.Should().BeGreaterThan(13UL);
(groupBValue % 10UL).Should().NotBe(3UL);
}
}
[Theory]
[Trait("Category", "Fast")]
[InlineData((byte)1)]
[InlineData((byte)7)]
[InlineData((byte)9)]
public void Three_a_two_b_pattern_interleaves_three_group_a_values_before_two_group_b_values(byte lastDigit)
{
ReadOnlySpan<ulong> combined = HeuristicCombinedPrimeTester.GetCombinedDivisors(
lastDigit,
HeuristicCombinedPrimeTester.CombinedDivisorPattern.ThreeATwoB);
combined.Length.Should().BeGreaterThan(28);
combined[0].Should().Be(3UL);
combined[1].Should().Be(7UL);
combined[2].Should().Be(11UL);
combined[3].Should().Be(13UL);
int index = 4;
const int segmentsToCheck = 4;
for (int segment = 0; segment < segmentsToCheck; segment++)
{
for (int i = 0; i < 3; i++)
{
ulong groupAValue = combined[index++];
groupAValue.Should().BeGreaterThan(13UL);
(groupAValue % 10UL).Should().Be(3UL);
}
for (int i = 0; i < 2; i++)
{
ulong groupBValue = combined[index++];
groupBValue.Should().BeGreaterThan(13UL);
(groupBValue % 10UL).Should().NotBe(3UL);
}
}
}
[Theory]
[Trait("Category", "Fast")]
[InlineData((byte)1)]
[InlineData((byte)7)]
[InlineData((byte)9)]
public void Three_a_one_b_pattern_interleaves_three_group_a_values_before_group_b(byte lastDigit)
{
ReadOnlySpan<ulong> combined = HeuristicCombinedPrimeTester.GetCombinedDivisors(
lastDigit,
HeuristicCombinedPrimeTester.CombinedDivisorPattern.ThreeAOneB);
combined.Length.Should().BeGreaterThan(24);
combined[0].Should().Be(3UL);
combined[1].Should().Be(7UL);
combined[2].Should().Be(11UL);
combined[3].Should().Be(13UL);
int index = 4;
const int segmentsToCheck = 4;
for (int segment = 0; segment < segmentsToCheck; segment++)
{
for (int i = 0; i < 3; i++)
{
ulong groupAValue = combined[index++];
groupAValue.Should().BeGreaterThan(13UL);
(groupAValue % 10UL).Should().Be(3UL);
}
ulong groupBValue = combined[index++];
groupBValue.Should().BeGreaterThan(13UL);
(groupBValue % 10UL).Should().NotBe(3UL);
}
}
[Theory]
[Trait("Category", "Fast")]
[InlineData(0UL)]
[InlineData(1UL)]
[InlineData(2UL)]
[InlineData(3UL)]
[InlineData(4UL)]
[InlineData(5UL)]
[InlineData(8UL)]
[InlineData(9UL)]
[InlineData(15UL)]
[InlineData(16UL)]
[InlineData(17UL)]
[InlineData(24UL)]
[InlineData(25UL)]
[InlineData(26UL)]
[InlineData(255UL)]
[InlineData(256UL)]
[InlineData(257UL)]
[InlineData(1_000UL)]
[InlineData(1_048_575UL)]
[InlineData(1_048_576UL)]
[InlineData(1_048_577UL)]
[InlineData(4_000_000UL)]
[InlineData(4_000_001UL)]
[InlineData(4_000_004UL)]
[InlineData(65_535_999UL)]
[InlineData(65_536_000UL)]
[InlineData(65_536_001UL)]
[InlineData(4_294_967_295UL)]
[InlineData(4_294_967_296UL)]
[InlineData(4_294_967_297UL)]
[InlineData(18_446_744_073_709_551_615UL)]
[InlineData(18_446_744_073_709_551_614UL)]
[InlineData(18_446_744_073_709_551_613UL)]
public void ComputeHeuristicDivisorSquareLimit_returns_input_value(ulong value)
{
ulong limit = HeuristicCombinedPrimeTester.ComputeHeuristicDivisorSquareLimit(value);
limit.Should().Be(value);
}
}
@@ -11,7 +11,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
{ {
var candidates = new List<ulong> { 5UL, 7UL, 11UL, 13UL }; var candidates = new List<ulong> { 5UL, 7UL, 11UL, 13UL };
var tester = new FakeTester(); var tester = new FakeTester();
var results = new List<(ulong Prime, bool Searched, bool Detailed, bool Passed)>(); var results = new List<(ulong Prime, bool Searched, bool Detailed, bool Passed, ulong Divisor)>();
int compositeMarks = 0; int compositeMarks = 0;
int clearedMarks = 0; int clearedMarks = 0;
@@ -22,7 +22,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
startPrime: 0UL, startPrime: 0UL,
markComposite: () => compositeMarks++, markComposite: () => compositeMarks++,
clearComposite: () => clearedMarks++, clearComposite: () => clearedMarks++,
printResult: (prime, searched, detailed, passed) => results.Add((prime, searched, detailed, passed)), printResult: (prime, searched, detailed, passed, divisor) => results.Add((prime, searched, detailed, passed, divisor)),
threadCount: 1, threadCount: 1,
primesPerTask: 2); primesPerTask: 2);
@@ -32,10 +32,10 @@ public class MersenneNumberDivisorByDivisorTesterTests
results.Should().Equal( results.Should().Equal(
new[] new[]
{ {
(5UL, true, true, true), (5UL, true, true, true, 0UL),
(7UL, true, true, false), (7UL, true, true, false, 7UL),
(11UL, true, true, false), (11UL, true, true, false, 11UL),
(13UL, true, true, true), (13UL, true, true, true, 0UL),
}); });
} }
@@ -57,10 +57,23 @@ public class MersenneNumberDivisorByDivisorTesterTests
return DivisorLimit; return DivisorLimit;
} }
public bool IsPrime(ulong prime, out bool divisorsExhausted) public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor)
{ {
divisorsExhausted = true; divisorsExhausted = true;
return prime % 7UL != 0UL && prime % 11UL != 0UL; if (prime % 7UL == 0UL)
{
divisor = 7UL;
return false;
}
if (prime % 11UL == 0UL)
{
divisor = 11UL;
return false;
}
divisor = 0UL;
return true;
} }
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession() public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
@@ -83,7 +96,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
throw new NotSupportedException(); throw new NotSupportedException();
} }
public void Dispose() public void Return()
{ {
} }
} }
@@ -13,14 +13,17 @@ public class MersenneNumberDivisorCpuTesterTests
var tester = new MersenneNumberDivisorByDivisorCpuTester(); var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(11UL); tester.ConfigureFromMaxPrime(11UL);
tester.IsPrime(5UL, out bool divisorsExhausted).Should().BeTrue(); tester.IsPrime(5UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().Be(0UL);
tester.IsPrime(7UL, out divisorsExhausted).Should().BeTrue(); tester.IsPrime(7UL, out divisorsExhausted, out divisor).Should().BeTrue();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().Be(0UL);
tester.IsPrime(11UL, out divisorsExhausted).Should().BeFalse(); tester.IsPrime(11UL, out divisorsExhausted, out divisor).Should().BeFalse();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
} }
[Fact] [Fact]
@@ -30,7 +33,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 +48,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.Return();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -51,7 +61,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 +73,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.Return();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -69,7 +86,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 +98,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.Return();
}
}
} }
@@ -73,14 +73,17 @@ public class MersenneNumberDivisorGpuTesterTests
var tester = new MersenneNumberDivisorByDivisorGpuTester(); var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(43UL); tester.ConfigureFromMaxPrime(43UL);
tester.IsPrime(31UL, out bool exhausted).Should().BeTrue(); tester.IsPrime(31UL, out bool exhausted, out ulong divisor).Should().BeTrue();
exhausted.Should().BeTrue(); exhausted.Should().BeTrue();
divisor.Should().Be(0UL);
tester.IsPrime(37UL, out exhausted).Should().BeFalse(); tester.IsPrime(37UL, out exhausted, out divisor).Should().BeFalse();
exhausted.Should().BeTrue(); exhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
tester.IsPrime(43UL, out exhausted).Should().BeFalse(); tester.IsPrime(43UL, out exhausted, out divisor).Should().BeFalse();
exhausted.Should().BeTrue(); exhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
} }
[Fact] [Fact]
@@ -105,12 +108,15 @@ public class MersenneNumberDivisorGpuTesterTests
var tester = new MersenneNumberDivisorByDivisorGpuTester(); var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(41UL); tester.ConfigureFromMaxPrime(41UL);
tester.IsPrime(31UL, out _).Should().BeTrue(); tester.IsPrime(31UL, out _, out ulong divisor).Should().BeTrue();
tester.IsPrime(37UL, out bool exhausted).Should().BeFalse(); divisor.Should().Be(0UL);
tester.IsPrime(37UL, out bool exhausted, out divisor).Should().BeFalse();
exhausted.Should().BeTrue(); exhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse(); tester.IsPrime(41UL, out bool divisorsExhausted, out divisor).Should().BeFalse();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
} }
@@ -124,8 +130,16 @@ public class MersenneNumberDivisorGpuTesterTests
var tester = new MersenneNumberDivisorByDivisorGpuTester(); var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(exponent); tester.ConfigureFromMaxPrime(exponent);
tester.IsPrime(exponent, out bool exhausted).Should().Be(expectedPrime); tester.IsPrime(exponent, out bool exhausted, out ulong divisor).Should().Be(expectedPrime);
exhausted.Should().BeTrue(); exhausted.Should().BeTrue();
if (expectedPrime)
{
divisor.Should().Be(0UL);
}
else
{
divisor.Should().BeGreaterThan(0UL);
}
} }
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -138,8 +152,9 @@ public class MersenneNumberDivisorGpuTesterTests
tester.ConfigureFromMaxPrime(43UL); tester.ConfigureFromMaxPrime(43UL);
tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse(); tester.IsPrime(41UL, out bool divisorsExhausted, out ulong divisor).Should().BeFalse();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
} }
[Fact] [Fact]
@@ -149,7 +164,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 +174,11 @@ public class MersenneNumberDivisorGpuTesterTests
hits.Should().ContainInOrder(new byte[] { 0, 1, 0 }); hits.Should().ContainInOrder(new byte[] { 0, 1, 0 });
} }
finally
{
session.Return();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -165,7 +187,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 +202,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.Return();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -190,7 +219,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 +230,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.Return();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -210,7 +246,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 +259,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.Return();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -232,7 +275,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 +288,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.Return();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -274,15 +324,15 @@ public class MersenneNumberDivisorGpuTesterTests
.GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)! .GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)!
.SetValue(tester, 200UL); .SetValue(tester, 200UL);
tester.IsPrime(19UL, out bool divisorsExhausted).Should().BeTrue(); tester.IsPrime(19UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().Be(0UL);
} }
finally finally
{ {
tableField.SetValue(cycles, originalTable); tableField.SetValue(cycles, originalTable);
smallCyclesField.SetValue(cycles, originalSmall); smallCyclesField.SetValue(cycles, originalSmall);
DivisorCycleCache.Shared.RefreshSnapshot(); DivisorCycleCache.Shared.RefreshSnapshot();
GpuContextPool.DisposeAll();
} }
} }
@@ -297,7 +347,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 +358,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.Return();
}
}
} }
@@ -1,5 +1,4 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Cpu; using PerfectNumbers.Core.Cpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -1,5 +1,4 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -1,5 +1,4 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Cpu; using PerfectNumbers.Core.Cpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -1,6 +1,5 @@
using System.Reflection; using System.Reflection;
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -55,7 +54,6 @@ public class MersenneNumberOrderGpuTesterTests
ulong[]? backupSmall = originalSmall is null ? null : (ulong[])originalSmall.Clone(); ulong[]? backupSmall = originalSmall is null ? null : (ulong[])originalSmall.Clone();
cycles.LoadFrom(tempPath); cycles.LoadFrom(tempPath);
GpuContextPool.DisposeAll();
try try
{ {
@@ -75,7 +73,6 @@ public class MersenneNumberOrderGpuTesterTests
{ {
tableField.SetValue(cycles, originalTable); tableField.SetValue(cycles, originalTable);
smallCyclesField.SetValue(cycles, backupSmall); smallCyclesField.SetValue(cycles, backupSmall);
GpuContextPool.DisposeAll();
} }
} }
finally finally
@@ -1,5 +1,4 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Cpu; using PerfectNumbers.Core.Cpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -1,5 +1,4 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -17,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);
}
}
}
@@ -1,6 +1,6 @@
namespace PerfectNumbers.Core; namespace PerfectNumbers.Core.Cpu;
internal static class DeterministicRandom internal static class DeterministicRandomCpu
{ {
[ThreadStatic] [ThreadStatic]
private static ulong s_threadState; private static ulong s_threadState;
@@ -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(
@@ -65,14 +62,8 @@ internal sealed class MersenneCpuDivisorScanSession : IMersenneNumberDivisorByDi
} }
} }
public void Dispose() public void Return()
{ {
if (_disposed)
{
throw new ObjectDisposedException(nameof(MersenneCpuDivisorScanSession), "Divisor scan session disposed twice.");
}
_disposed = true;
ThreadStaticPools.ReturnMersenneCpuDivisorSession(this); ThreadStaticPools.ReturnMersenneCpuDivisorSession(this);
} }
} }
@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Cpu; namespace PerfectNumbers.Core.Cpu;
@@ -43,7 +42,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit); public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit);
public bool IsPrime(ulong prime, out bool divisorsExhausted) public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor)
{ {
ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit); ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
@@ -61,7 +60,8 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
bool composite = CheckDivisors( bool composite = CheckDivisors(
prime, prime,
allowedMax, allowedMax,
out processedAll); out processedAll,
out divisor);
if (composite) if (composite)
{ {
@@ -70,6 +70,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
} }
divisorsExhausted = processedAll || composite; divisorsExhausted = processedAll || composite;
divisor = 0UL;
return true; return true;
} }
@@ -97,10 +98,10 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
private bool CheckDivisors( private bool CheckDivisors(
ulong prime, ulong prime,
ulong allowedMax, ulong allowedMax,
out bool processedAll) out bool processedAll,
out ulong foundDivisor)
{ {
processedAll = false; foundDivisor = 0UL;
// The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs. // The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs.
// Keeping the guard commented out documents the reasoning for benchmarks and tests. // Keeping the guard commented out documents the reasoning for benchmarks and tests.
// if (allowedMax < 3UL) // if (allowedMax < 3UL)
@@ -126,49 +127,91 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
if (divisor.CompareTo(limit) > 0) if (divisor.CompareTo(limit) > 0)
{ {
processedAll = true; processedAll = true;
foundDivisor = 0UL;
return false; return false;
} }
// Intentionally recomputes factorizations without a per-thread cache. // Intentionally recomputes factorizations without a per-thread cache.
// The previous factor cache recorded virtually no hits and only slowed down the scan. // The previous factor cache recorded virtually no hits and only slowed down the scan.
DivisorCycleCache cycleCache = DivisorCycleCache.Shared; _ = DivisorCycleCache.Shared;
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;
ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit); ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit);
byte step10;
byte step8;
byte step3;
byte step7;
byte step11;
byte remainder10;
byte remainder8;
byte remainder3;
byte remainder7;
byte remainder11;
if (stepHigh == 0UL && limitHigh == 0UL)
{
step10 = (byte)(stepLow % 10UL);
step8 = (byte)(stepLow & 7UL);
step3 = (byte)(stepLow % 3UL);
step7 = (byte)(stepLow % 7UL);
step11 = (byte)(stepLow % 11UL);
remainder10 = (byte)(divisorLow % 10UL);
remainder8 = (byte)(divisorLow & 7UL);
remainder3 = (byte)(divisorLow % 3UL);
remainder7 = (byte)(divisorLow % 7UL);
remainder11 = (byte)(divisorLow % 11UL);
return CheckDivisors64(
prime,
stepLow,
limit.Low,
divisorLow,
decimalMask,
step10,
step8,
step3,
step7,
step11,
remainder10,
remainder8,
remainder3,
remainder7,
remainder11,
out processedAll,
out foundDivisor);
}
ulong divisorHigh = divisor.High;
step10 = (byte)((((stepHigh % 10UL) * 6UL) + (stepLow % 10UL)) % 10UL);
step8 = (byte)(stepLow % 8UL);
step3 = (byte)(((stepHigh % 3UL) + (stepLow % 3UL)) % 3UL);
step7 = (byte)((((stepHigh % 7UL) * 2UL) + (stepLow % 7UL)) % 7UL);
step11 = (byte)((((stepHigh % 11UL) * 5UL) + (stepLow % 11UL)) % 11UL);
remainder10 = (byte)((((divisorHigh % 10UL) * 6UL) + (divisorLow % 10UL)) % 10UL);
remainder8 = (byte)(divisorLow % 8UL);
remainder3 = (byte)(((divisorHigh % 3UL) + (divisorLow % 3UL)) % 3UL);
remainder7 = (byte)((((divisorHigh % 7UL) * 2UL) + (divisorLow % 7UL)) % 7UL);
remainder11 = (byte)((((divisorHigh % 11UL) * 5UL) + (divisorLow % 11UL)) % 11UL);
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
while (divisor.CompareTo(limit) <= 0) while (divisor.CompareTo(limit) <= 0)
{ {
ulong candidate = divisor.Low; bool passesSmallModuli = remainder3 != 0 && remainder7 != 0 && remainder11 != 0;
if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
bool admissible = ((decimalMask >> remainder10) & 1) != 0
&& (remainder8 == 1 || remainder8 == 7)
&& remainder3 != 0
&& remainder5 != 0
&& remainder7 != 0
&& remainder11 != 0;
if (admissible)
{ {
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.
@@ -179,6 +222,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
// else // else
{ {
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu( if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
gpu,
candidate, candidate,
prime, prime,
divisorData, divisorData,
@@ -202,7 +246,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
{ {
// A cycle equal to the tested exponent (which is prime in this path) guarantees that the candidate divides // A cycle equal to the tested exponent (which is prime in this path) guarantees that the candidate divides
// the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p. // the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p.
foundDivisor = candidate;
processedAll = true; processedAll = true;
PrimeOrderCalculatorAccelerator.Return(gpu);
return true; return true;
} }
@@ -213,18 +259,154 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
} }
divisor.Add(step); divisor.Add(step);
remainder10 = AddMod(remainder10, step10, (byte)10); remainder10 = AddMod10(remainder10, step10);
remainder8 = AddMod8(remainder8, step8); 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 = divisor.CompareTo(limit) > 0; PrimeOrderCalculatorAccelerator.Return(gpu);
processedAll = true;
foundDivisor = 0UL;
return false; return false;
} }
private static bool CheckDivisors64(
ulong prime,
ulong step,
ulong limit,
ulong divisor,
ushort decimalMask,
byte step10,
byte step8,
byte step3,
byte step7,
byte step11,
byte remainder10,
byte remainder8,
byte remainder3,
byte remainder7,
byte remainder11,
out bool processedAll,
out ulong foundDivisor)
{
processedAll = false;
foundDivisor = 0UL;
bool canAdvance = step <= limit;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
if (!canAdvance)
{
if (divisor <= limit)
{
if (remainder3 != 0 && remainder7 != 0 && remainder11 != 0 && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
{
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
ulong divisorCycle;
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
gpu,
divisor,
prime,
divisorData,
out ulong computedCycle,
out bool primeOrderFailed) || computedCycle == 0UL)
{
divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
divisor,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
}
else
{
divisorCycle = computedCycle;
}
if (divisorCycle == prime)
{
foundDivisor = divisor;
processedAll = true;
PrimeOrderCalculatorAccelerator.Return(gpu);
return true;
}
if (divisorCycle == 0UL)
{
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
}
}
}
processedAll = true;
foundDivisor = 0UL;
return false;
}
if (divisor > limit)
{
processedAll = true;
foundDivisor = 0UL;
return false;
}
ulong remainingIterations = ((limit - divisor) / step) + 1UL;
while (true)
{
bool passesSmallModuli = remainder3 != 0 && remainder7 != 0 && remainder11 != 0;
if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
{
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
ulong divisorCycle;
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
gpu,
divisor,
prime,
divisorData,
out ulong computedCycle,
out bool primeOrderFailed) || computedCycle == 0UL)
{
divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
divisor,
divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
}
else
{
divisorCycle = computedCycle;
}
if (divisorCycle == prime)
{
foundDivisor = divisor;
processedAll = true;
PrimeOrderCalculatorAccelerator.Return(gpu);
return true;
}
if (divisorCycle == 0UL)
{
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
}
}
remainingIterations--;
if (remainingIterations == 0UL)
{
processedAll = true;
foundDivisor = 0UL;
return false;
}
divisor += step;
remainder10 = AddMod10(remainder10, step10);
remainder8 = AddMod8(remainder8, step8);
remainder3 = AddMod3(remainder3, step3);
remainder7 = AddMod7(remainder7, step7);
remainder11 = AddMod11(remainder11, step11);
}
}
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData) private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
{ {
ulong residue = prime.Pow2MontgomeryModWithCycleCpu(divisorCycle, divisorData); ulong residue = prime.Pow2MontgomeryModWithCycleCpu(divisorCycle, divisorData);
@@ -232,13 +414,57 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : 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)
{ {
const int Modulus = 3;
int sum = value + delta; int sum = value + delta;
if (sum >= modulus) if (sum >= Modulus)
{ {
sum -= modulus; sum -= Modulus;
}
return (byte)sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod7(byte value, byte delta)
{
const int Modulus = 7;
int sum = value + delta;
if (sum >= Modulus)
{
sum -= Modulus;
}
return (byte)sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod10(byte value, byte delta)
{
const int Modulus = 10;
int sum = value + delta;
if (sum >= Modulus)
{
sum -= Modulus;
}
return (byte)sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod11(byte value, byte delta)
{
const int Modulus = 11;
int sum = value + delta;
if (sum >= Modulus)
{
sum -= Modulus;
} }
return (byte)sum; return (byte)sum;
+94 -48
View File
@@ -1,11 +1,8 @@
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core; namespace PerfectNumbers.Core;
@@ -14,8 +11,9 @@ public sealed class DivisorCycleCache
private const byte ByteZero = 0; private const byte ByteZero = 0;
private const byte ByteOne = 1; private const byte ByteOne = 1;
private const int StackBufferThreshold = 128; private const int StackBufferThreshold = 128;
private readonly Accelerator _accelerator;
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>> _gpuKernelCache = new(AcceleratorReferenceComparer.Instance); private readonly int _acceleratorIndex;
private readonly Action<AcceleratorStream, Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>> _gpuKernel;
private volatile ulong[] _snapshot; private volatile ulong[] _snapshot;
private volatile bool _useGpuGeneration = true; private volatile bool _useGpuGeneration = true;
private readonly int _divisorCyclesBatchSize; private readonly int _divisorCyclesBatchSize;
@@ -36,9 +34,16 @@ public sealed class DivisorCycleCache
public int PreferredBatchSize => _divisorCyclesBatchSize; public int PreferredBatchSize => _divisorCyclesBatchSize;
// private static readonly Accelerator[] _accelerators;
private DivisorCycleCache(int divisorCyclesBatchSize) private DivisorCycleCache(int divisorCyclesBatchSize)
{ {
_divisorCyclesBatchSize = Math.Max(1, divisorCyclesBatchSize); _divisorCyclesBatchSize = Math.Max(1, divisorCyclesBatchSize);
var acceleratorIndex = AcceleratorPool.Shared.Rent();
_acceleratorIndex = acceleratorIndex;
Accelerator accelerator = AcceleratorPool.Shared.Accelerators[acceleratorIndex];
_accelerator = accelerator;
_gpuKernel = LoadKernel(accelerator);
_snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); _snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
} }
@@ -79,7 +84,6 @@ public sealed class DivisorCycleCache
throw new InvalidDataException($"Divisor cycle is missing for {divisor}"); throw new InvalidDataException($"Divisor cycle is missing for {divisor}");
} }
// ObserveCycleCacheDivisor(divisor);
return cached; return cached;
} }
@@ -94,7 +98,7 @@ public sealed class DivisorCycleCache
int[]? rentedMissing = null; int[]? rentedMissing = null;
Span<int> missingBuffer = length <= StackBufferThreshold Span<int> missingBuffer = length <= StackBufferThreshold
? stackalloc int[length] ? stackalloc int[length]
: new Span<int>(rentedMissing = ArrayPool<int>.Shared.Rent(length), 0, length); : new Span<int>(rentedMissing = ThreadStaticPools.IntPool.Rent(length), 0, length);
int missingCount = 0; int missingCount = 0;
@@ -114,7 +118,6 @@ public sealed class DivisorCycleCache
throw new InvalidDataException($"Divisor cycle is missing for {divisor}"); throw new InvalidDataException($"Divisor cycle is missing for {divisor}");
} }
// ObserveCycleCacheDivisor(divisor);
cycles[i] = cached; cycles[i] = cached;
} }
else else
@@ -138,7 +141,7 @@ public sealed class DivisorCycleCache
if (rentedMissing is not null) if (rentedMissing is not null)
{ {
ArrayPool<int>.Shared.Return(rentedMissing, clearArray: false); ThreadStaticPools.IntPool.Return(rentedMissing, clearArray: false);
} }
} }
@@ -197,12 +200,12 @@ public sealed class DivisorCycleCache
ulong[]? rentedDivisors = null; ulong[]? rentedDivisors = null;
Span<ulong> workDivisors = count <= StackBufferThreshold Span<ulong> workDivisors = count <= StackBufferThreshold
? stackalloc ulong[count] ? stackalloc ulong[count]
: new Span<ulong>(rentedDivisors = ArrayPool<ulong>.Shared.Rent(count), 0, count); : new Span<ulong>(rentedDivisors = ThreadStaticPools.UlongPool.Rent(count), 0, count);
ulong[]? rentedResults = null; ulong[]? rentedResults = null;
Span<ulong> workResults = count <= StackBufferThreshold Span<ulong> workResults = count <= StackBufferThreshold
? stackalloc ulong[count] ? stackalloc ulong[count]
: new Span<ulong>(rentedResults = ArrayPool<ulong>.Shared.Rent(count), 0, count); : new Span<ulong>(rentedResults = ThreadStaticPools.UlongPool.Rent(count), 0, count);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
@@ -218,48 +221,80 @@ public sealed class DivisorCycleCache
if (rentedDivisors is not null) if (rentedDivisors is not null)
{ {
ArrayPool<ulong>.Shared.Return(rentedDivisors, clearArray: false); ThreadStaticPools.UlongPool.Return(rentedDivisors, clearArray: false);
ArrayPool<ulong>.Shared.Return(rentedResults!, clearArray: false); ThreadStaticPools.UlongPool.Return(rentedResults!, clearArray: false);
}
}
private const int DefaultCapacity = 128;
[ThreadStatic]
private static MemoryBuffer1D<ulong, Stride1D.Dense>? divisorBuffer;
[ThreadStatic]
private static MemoryBuffer1D<ulong, Stride1D.Dense>? powBuffer;
[ThreadStatic]
private static MemoryBuffer1D<ulong, Stride1D.Dense>? orderBuffer;
[ThreadStatic]
private static MemoryBuffer1D<ulong, Stride1D.Dense>? resultBuffer;
[ThreadStatic]
private static MemoryBuffer1D<byte, Stride1D.Dense>? statusBuffer;
private void EnsureCapacity(int requiredCapacity)
{
if (requiredCapacity <= (divisorBuffer?.Length ?? 0))
{
return;
}
if (divisorBuffer != null)
{
divisorBuffer.Dispose();
powBuffer!.Dispose();
orderBuffer!.Dispose();
resultBuffer!.Dispose();
statusBuffer!.Dispose();
}
Accelerator accelerator = _accelerator;
lock(accelerator)
{
divisorBuffer = accelerator.Allocate1D<ulong>(requiredCapacity);
powBuffer = accelerator.Allocate1D<ulong>(requiredCapacity);
orderBuffer = accelerator.Allocate1D<ulong>(requiredCapacity);
resultBuffer = accelerator.Allocate1D<ulong>(requiredCapacity);
statusBuffer = accelerator.Allocate1D<byte>(requiredCapacity);
} }
} }
private void ComputeCyclesGpuCore(ReadOnlySpan<ulong> divisors, Span<ulong> destination) private void ComputeCyclesGpuCore(ReadOnlySpan<ulong> divisors, Span<ulong> destination)
{ {
int length = divisors.Length; int length = divisors.Length;
var gpuLease = GpuKernelPool.GetKernel(useGpuOrder: true);
var execution = gpuLease.EnterExecutionScope();
Accelerator accelerator = gpuLease.Accelerator; EnsureCapacity(length);
var kernel = _gpuKernelCache.GetOrAdd(accelerator, LoadKernel);
using MemoryBuffer1D<ulong, Stride1D.Dense> divisorBuffer = accelerator.Allocate1D<ulong>(length);
using MemoryBuffer1D<ulong, Stride1D.Dense> powBuffer = accelerator.Allocate1D<ulong>(length);
using MemoryBuffer1D<ulong, Stride1D.Dense> orderBuffer = accelerator.Allocate1D<ulong>(length);
using MemoryBuffer1D<ulong, Stride1D.Dense> resultBuffer = accelerator.Allocate1D<ulong>(length);
using MemoryBuffer1D<byte, Stride1D.Dense> statusBuffer = accelerator.Allocate1D<byte>(length);
ulong[]? rentedPow = null; ulong[]? rentedPow = null;
Span<ulong> powSpan = length <= StackBufferThreshold Span<ulong> powSpan = length <= StackBufferThreshold
? stackalloc ulong[length] ? stackalloc ulong[length]
: new Span<ulong>(rentedPow = ArrayPool<ulong>.Shared.Rent(length), 0, length); : new Span<ulong>(rentedPow = ThreadStaticPools.UlongPool.Rent(length), 0, length);
ulong[]? rentedOrder = null; ulong[]? rentedOrder = null;
Span<ulong> orderSpan = length <= StackBufferThreshold Span<ulong> orderSpan = length <= StackBufferThreshold
? stackalloc ulong[length] ? stackalloc ulong[length]
: new Span<ulong>(rentedOrder = ArrayPool<ulong>.Shared.Rent(length), 0, length); : new Span<ulong>(rentedOrder = ThreadStaticPools.UlongPool.Rent(length), 0, length);
ulong[]? rentedResult = null; ulong[]? rentedResult = null;
Span<ulong> resultSpan = length <= StackBufferThreshold Span<ulong> resultSpan = length <= StackBufferThreshold
? stackalloc ulong[length] ? stackalloc ulong[length]
: new Span<ulong>(rentedResult = ArrayPool<ulong>.Shared.Rent(length), 0, length); : new Span<ulong>(rentedResult = ThreadStaticPools.UlongPool.Rent(length), 0, length);
byte[]? rentedStatus = null; byte[]? rentedStatus = null;
Span<byte> statusSpan = length <= StackBufferThreshold Span<byte> statusSpan = length <= StackBufferThreshold
? stackalloc byte[length] ? stackalloc byte[length]
: new Span<byte>(rentedStatus = ArrayPool<byte>.Shared.Rent(length), 0, length); : new Span<byte>(rentedStatus = ThreadStaticPools.BytePool.Rent(length), 0, length);
ref ulong divisorRef = ref MemoryMarshal.GetReference(divisors);
divisorBuffer.View.CopyFromCPU(ref divisorRef, length);
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
@@ -276,18 +311,25 @@ public sealed class DivisorCycleCache
statusSpan[i] = ByteZero; statusSpan[i] = ByteZero;
} }
powBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(powSpan), length); // GpuPrimeWorkLimiter.Acquire();
orderBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(orderSpan), length);
resultBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(resultSpan), length);
statusBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(statusSpan), length);
int pending; int pending;
var kernel = _gpuKernel;
var acceleratorIndex = _acceleratorIndex;
var stream = AcceleratorStreamPool.Rent(acceleratorIndex);
divisorBuffer!.View.CopyFromCPU(stream, divisors);
powBuffer!.View.CopyFromCPU(stream, powSpan);
orderBuffer!.View.CopyFromCPU(stream, orderSpan);
resultBuffer!.View.CopyFromCPU(stream, resultSpan);
statusBuffer!.View.CopyFromCPU(stream, statusSpan);
do do
{ {
kernel(length, _divisorCyclesBatchSize, divisorBuffer.View, powBuffer.View, orderBuffer.View, resultBuffer.View, statusBuffer.View); kernel(stream, length, _divisorCyclesBatchSize, divisorBuffer.View, powBuffer.View, orderBuffer.View, resultBuffer.View, statusBuffer.View);
accelerator.Synchronize();
statusBuffer.View.CopyToCPU(stream, statusSpan);
stream.Synchronize();
statusBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(statusSpan), length);
pending = 0; pending = 0;
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
@@ -299,23 +341,27 @@ public sealed class DivisorCycleCache
} }
while (pending > 0); while (pending > 0);
resultBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(resultSpan), length); resultBuffer.View.CopyToCPU(stream, resultSpan);
stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
resultSpan.CopyTo(destination); resultSpan.CopyTo(destination);
if (rentedPow is not null) if (rentedPow is not null)
{ {
ArrayPool<ulong>.Shared.Return(rentedPow, clearArray: false); ThreadStaticPools.UlongPool.Return(rentedPow, clearArray: false);
ArrayPool<ulong>.Shared.Return(rentedOrder!, clearArray: false); ThreadStaticPools.UlongPool.Return(rentedOrder!, clearArray: false);
ArrayPool<ulong>.Shared.Return(rentedResult!, clearArray: false); ThreadStaticPools.UlongPool.Return(rentedResult!, clearArray: false);
ArrayPool<byte>.Shared.Return(rentedStatus!, clearArray: false); ThreadStaticPools.BytePool.Return(rentedStatus!, clearArray: false);
} }
execution.Dispose(); // GpuPrimeWorkLimiter.Release();
gpuLease.Dispose();
} }
private static Action<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>> LoadKernel(Accelerator accelerator) private static Action<AcceleratorStream, Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>> LoadKernel(Accelerator accelerator)
{ {
return accelerator.LoadAutoGroupedStreamKernel<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>(DivisorCycleKernels.GpuAdvanceDivisorCyclesKernel); var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>(DivisorCycleKernels.GpuAdvanceDivisorCyclesKernel);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>>();
} }
private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator> private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
+248 -16
View File
@@ -4,40 +4,272 @@ namespace PerfectNumbers.Core;
internal static class DivisorGenerator internal static class DivisorGenerator
{ {
public static readonly uint[] SmallPrimes;
public static readonly ulong[] SmallPrimesPow2;
public static readonly uint[] SmallPrimesLastOne;
public static readonly ulong[] SmallPrimesPow2LastOne;
public static readonly uint[] SmallPrimesLastSeven;
public static readonly ulong[] SmallPrimesPow2LastSeven;
public static readonly uint[] SmallPrimesLastThree;
public static readonly ulong[] SmallPrimesPow2LastThree;
public static readonly uint[] SmallPrimesLastNine;
public static readonly ulong[] SmallPrimesPow2LastNine;
public static readonly uint[] SmallPrimesLastOneWithoutLastThree;
public static readonly ulong[] SmallPrimesPow2LastOneWithoutLastThree;
public static readonly uint[] SmallPrimesLastSevenWithoutLastThree;
public static readonly ulong[] SmallPrimesPow2LastSevenWithoutLastThree;
public static readonly uint[] SmallPrimesLastNineWithoutLastThree;
public static readonly ulong[] SmallPrimesPow2LastNineWithoutLastThree;
private const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9); private const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9);
private const ushort DecimalMaskWhenLastIsThree = (1 << 1) | (1 << 3) | (1 << 7) | (1 << 9);
private const ushort DecimalMaskWhenLastIsNine = (1 << 1) | (1 << 3) | (1 << 7) | (1 << 9);
private const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 9); private const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 9);
static DivisorGenerator()
{
BuildSmallPrimes(
out SmallPrimes,
out SmallPrimesPow2,
out SmallPrimesLastOne,
out SmallPrimesPow2LastOne,
out SmallPrimesLastSeven,
out SmallPrimesPow2LastSeven,
out SmallPrimesLastThree,
out SmallPrimesPow2LastThree,
out SmallPrimesLastNine,
out SmallPrimesPow2LastNine,
out SmallPrimesLastOneWithoutLastThree,
out SmallPrimesPow2LastOneWithoutLastThree,
out SmallPrimesLastSevenWithoutLastThree,
out SmallPrimesPow2LastSevenWithoutLastThree,
out SmallPrimesLastNineWithoutLastThree,
out SmallPrimesPow2LastNineWithoutLastThree);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort GetDecimalMask(LastDigit lastDigit) public static ushort GetDecimalMask(LastDigit lastDigit)
{ {
return lastDigit == LastDigit.Seven ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise; return lastDigit switch
{
LastDigit.Seven => DecimalMaskWhenLastIsSeven,
LastDigit.Three => DecimalMaskWhenLastIsThree,
LastDigit.Nine => DecimalMaskWhenLastIsNine,
_ => DecimalMaskOtherwise,
};
}
private static void BuildSmallPrimes(
out uint[] all,
out ulong[] allPow2,
out uint[] lastOne,
out ulong[] lastOnePow2,
out uint[] lastSeven,
out ulong[] lastSevenPow2,
out uint[] lastThree,
out ulong[] lastThreePow2,
out uint[] lastNine,
out ulong[] lastNinePow2,
out uint[] lastOneWithoutThree,
out ulong[] lastOneWithoutThreePow2,
out uint[] lastSevenWithoutThree,
out ulong[] lastSevenWithoutThreePow2,
out uint[] lastNineWithoutThree,
out ulong[] lastNineWithoutThreePow2)
{
uint target = PerfectNumberConstants.PrimesLimit;
int targetInt = checked((int)target);
all = new uint[targetInt];
allPow2 = new ulong[targetInt];
lastOne = new uint[targetInt];
lastOnePow2 = new ulong[targetInt];
lastSeven = new uint[targetInt];
lastSevenPow2 = new ulong[targetInt];
lastThree = new uint[targetInt];
lastThreePow2 = new ulong[targetInt];
lastNine = new uint[targetInt];
lastNinePow2 = new ulong[targetInt];
List<uint> primes = new(targetInt * 4 / 3 + 16);
List<uint> lastOneWithoutThreeList = new(targetInt);
List<ulong> lastOneWithoutThreePow2List = new(targetInt);
List<uint> lastSevenWithoutThreeList = new(targetInt);
List<ulong> lastSevenWithoutThreePow2List = new(targetInt);
List<uint> lastNineWithoutThreeList = new(targetInt);
List<ulong> lastNineWithoutThreePow2List = new(targetInt);
uint candidate = 2U;
int allCount = 0;
int lastOneCount = 0;
int lastSevenCount = 0;
int lastThreeCount = 0;
int lastNineCount = 0;
while (allCount < targetInt ||
lastOneCount < targetInt ||
lastSevenCount < targetInt ||
lastThreeCount < targetInt ||
lastNineCount < targetInt)
{
bool isPrime = true;
int primesCount = primes.Count;
for (int i = 0; i < primesCount; i++)
{
uint p = primes[i];
if (p * p > candidate)
{
break;
}
if (candidate % p == 0U)
{
isPrime = false;
break;
}
}
if (isPrime)
{
primes.Add(candidate);
ulong candidateSquared = checked((ulong)candidate * candidate);
if (allCount < targetInt)
{
all[allCount] = candidate;
allPow2[allCount] = candidateSquared;
allCount++;
}
if (candidate != 2U)
{
LastDigit lastDigit = GetLastDigit(candidate);
bool allowedForLastOne = IsAllowedForLastOne(candidate, lastDigit);
bool allowedForLastSeven = IsAllowedForLastSeven(candidate, lastDigit);
bool allowedForLastThree = IsAllowedForLastThree(candidate, lastDigit);
bool allowedForLastNine = IsAllowedForLastNine(candidate, lastDigit);
bool lastDigitIsThree = lastDigit == LastDigit.Three;
if (allowedForLastOne && lastOneCount < targetInt)
{
lastOne[lastOneCount] = candidate;
lastOnePow2[lastOneCount] = candidateSquared;
lastOneCount++;
}
if (allowedForLastSeven && lastSevenCount < targetInt)
{
lastSeven[lastSevenCount] = candidate;
lastSevenPow2[lastSevenCount] = candidateSquared;
lastSevenCount++;
}
if (allowedForLastThree && lastThreeCount < targetInt)
{
lastThree[lastThreeCount] = candidate;
lastThreePow2[lastThreeCount] = candidateSquared;
lastThreeCount++;
}
if (allowedForLastNine && lastNineCount < targetInt)
{
lastNine[lastNineCount] = candidate;
lastNinePow2[lastNineCount] = candidateSquared;
lastNineCount++;
}
if (!lastDigitIsThree && allowedForLastOne)
{
lastOneWithoutThreeList.Add(candidate);
lastOneWithoutThreePow2List.Add(candidateSquared);
}
if (!lastDigitIsThree && allowedForLastSeven)
{
lastSevenWithoutThreeList.Add(candidate);
lastSevenWithoutThreePow2List.Add(candidateSquared);
}
if (!lastDigitIsThree && allowedForLastNine)
{
lastNineWithoutThreeList.Add(candidate);
lastNineWithoutThreePow2List.Add(candidateSquared);
}
}
}
candidate = candidate == 2U ? 3U : candidate + 2U;
}
lastOneWithoutThree = lastOneWithoutThreeList.ToArray();
lastOneWithoutThreePow2 = lastOneWithoutThreePow2List.ToArray();
lastSevenWithoutThree = lastSevenWithoutThreeList.ToArray();
lastSevenWithoutThreePow2 = lastSevenWithoutThreePow2List.ToArray();
lastNineWithoutThree = lastNineWithoutThreeList.ToArray();
lastNineWithoutThreePow2 = lastNineWithoutThreePow2List.ToArray();
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsValidDivisor( private static LastDigit GetLastDigit(uint prime)
byte remainder10,
byte remainder8,
byte remainder3,
byte remainder5,
byte remainder7,
byte remainder11,
ushort decimalMask)
{ {
if (((decimalMask >> remainder10) & 1) == 0) return (prime % 10U) switch
{ {
return false; 1U => LastDigit.One,
3U => LastDigit.Three,
5U => LastDigit.Five,
7U => LastDigit.Seven,
9U => LastDigit.Nine,
_ => throw new ArgumentOutOfRangeException(nameof(prime)),
};
} }
if (remainder8 != 1 && remainder8 != 7) [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsAllowedForLastOne(uint prime, LastDigit lastDigit)
{ {
return false; // 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,
};
} }
if (remainder3 == 0 || remainder5 == 0 || remainder7 == 0 || remainder11 == 0) [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsAllowedForLastSeven(uint prime, LastDigit lastDigit)
{ {
return false; // 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,
};
} }
return true; [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);
} }
@@ -0,0 +1,61 @@
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu
{
public struct AcceleratorPool
{
internal readonly Context Context;
internal readonly Device Device;
internal readonly Accelerator[] Accelerators;
private int _index;
public int Rent()
{
Atomic.CompareExchange(ref _index, Capacity, 0);
var index = Atomic.Add(ref _index, 1);
if (index >= Capacity)
{
index -= Capacity;
}
return index;
}
public void Return(int acceleratorIndex)
{
// Intentionally left empty - there's nothing to do here. We're not really renting anything.
}
public static readonly AcceleratorPool Shared = new(PerfectNumberConstants.RollingAccelerators);
private readonly int Capacity;
public AcceleratorPool(int capacity)
{
Console.WriteLine("Creating accelerators");
Capacity = capacity;
var context = Context.CreateDefault();
var device = context.GetPreferredDevice(false);
Context = context;
Device = device;
int acceleratorsInContext = 0;
int acceleratorIndex = 0;
Accelerators = [.. Enumerable.Range(0, capacity).Select(_ =>
{
Console.WriteLine($"Preparing accelerator {acceleratorIndex++}");
Accelerator accelerator = device.CreateAccelerator(context);
acceleratorsInContext++;
if (acceleratorsInContext >= 128)
{
context = Context.CreateDefault();
device = context.GetPreferredDevice(false);
acceleratorsInContext = 0;
}
return accelerator;
})];
}
}
}
@@ -0,0 +1,13 @@
using System.Runtime.CompilerServices;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
internal sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
{
internal static AcceleratorReferenceComparer Instance { get; } = new();
public bool Equals(Accelerator? x, Accelerator? y) => ReferenceEquals(x, y);
public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj);
}
@@ -0,0 +1,34 @@
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu.Accelerators;
public static class AcceleratorExtensions
{
public static MemoryBuffer1D<ulong, Stride1D.Dense> CopySpanToDevice(this Accelerator accelerator, AcceleratorStream stream, in ulong[] span)
{
MemoryBuffer1D<ulong, Stride1D.Dense>? buffer;
buffer = accelerator.Allocate1D(stream, span);
return buffer;
}
public static MemoryBuffer1D<ulong, Stride1D.Dense> CopyUintSpanToDevice(this Accelerator accelerator, AcceleratorStream stream, ReadOnlySpan<uint> span)
{
MemoryBuffer1D<ulong, Stride1D.Dense>? buffer;
buffer = accelerator.Allocate1D<ulong>(span.Length);
if (!span.IsEmpty)
{
var converted = new ulong[span.Length];
for (int i = 0; i < span.Length; i++)
{
converted[i] = span[i];
}
buffer.View.CopyFromCPU(stream, converted);
}
return buffer;
}
}
@@ -0,0 +1,42 @@
using System.Collections.Concurrent;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu.Accelerators;
public static class AcceleratorStreamPool
{
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
private static readonly SemaphoreSlim[] _locks = new SemaphoreSlim[PerfectNumberConstants.RollingAccelerators];
private static readonly ConcurrentQueue<AcceleratorStream>[] _streams = new ConcurrentQueue<AcceleratorStream> [PerfectNumberConstants.RollingAccelerators];
private static SemaphoreSlim CreateLock() => new(PerfectNumberConstants.ThreadsByAccelerator);
// private static int _rented;
public static AcceleratorStream Rent(int acceleratorIndex)
{
var streamLock = _locks[acceleratorIndex];
var queue = _streams[acceleratorIndex];
streamLock.Wait();
return queue.TryDequeue(out var stream)
? stream
: _accelerators[acceleratorIndex].CreateStream();
}
public static void Return(int acceleratorIndex, AcceleratorStream stream)
{
var streamLock = _locks[acceleratorIndex];
var queue = _streams[acceleratorIndex];
queue.Enqueue(stream);
streamLock.Release();
}
public static void WarmUp(int acceleratorIndex)
{
_streams[acceleratorIndex] = new();
_locks[acceleratorIndex] = CreateLock();
}
}
@@ -0,0 +1,69 @@
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu.Accelerators;
internal readonly struct HeuristicCombinedGpuTables
{
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
private static readonly HeuristicCombinedGpuTables[] _sharedTables = new HeuristicCombinedGpuTables[PerfectNumberConstants.RollingAccelerators];
internal static void WarmUp(int acceleratorIndex, AcceleratorStream stream) => _sharedTables[acceleratorIndex] = new(_accelerators[acceleratorIndex], stream);
internal static HeuristicCombinedGpuTables GetStaticTables(int acceleratorIndex) => _sharedTables[acceleratorIndex];
internal HeuristicCombinedGpuTables(Accelerator accelerator, AcceleratorStream stream)
{
var combinedEnding1 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding1;
HeuristicCombinedDivisorsEnding1 = accelerator.CopySpanToDevice(stream, combinedEnding1);
var combinedSquaresEnding1 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding1Squares;
HeuristicCombinedDivisorSquaresEnding1 = accelerator.CopySpanToDevice(stream, combinedSquaresEnding1);
var combinedEnding3 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding3;
HeuristicCombinedDivisorsEnding3 = accelerator.CopySpanToDevice(stream, combinedEnding3);
var combinedSquaresEnding3 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding3Squares;
HeuristicCombinedDivisorSquaresEnding3 = accelerator.CopySpanToDevice(stream, combinedSquaresEnding3);
var combinedEnding7 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding7;
HeuristicCombinedDivisorsEnding7 = accelerator.CopySpanToDevice(stream, combinedEnding7);
var combinedSquaresEnding7 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding7Squares;
HeuristicCombinedDivisorSquaresEnding7 = accelerator.CopySpanToDevice(stream, combinedSquaresEnding7);
var combinedEnding9 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding9;
HeuristicCombinedDivisorsEnding9 = accelerator.CopySpanToDevice(stream, combinedEnding9);
var combinedSquaresEnding9 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding9Squares;
HeuristicCombinedDivisorSquaresEnding9 = accelerator.CopySpanToDevice(stream, combinedSquaresEnding9);
}
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding1;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding1;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding3;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding3;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding7;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding7;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding9;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding9;
internal HeuristicCombinedGpuViews CreateViews() => new(
HeuristicCombinedDivisorsEnding1.View,
HeuristicCombinedDivisorsEnding3.View,
HeuristicCombinedDivisorsEnding7.View,
HeuristicCombinedDivisorsEnding9.View,
HeuristicCombinedDivisorSquaresEnding1.View,
HeuristicCombinedDivisorSquaresEnding3.View,
HeuristicCombinedDivisorSquaresEnding7.View,
HeuristicCombinedDivisorSquaresEnding9.View);
internal void Dispose()
{
HeuristicCombinedDivisorsEnding1.Dispose();
HeuristicCombinedDivisorSquaresEnding1.Dispose();
HeuristicCombinedDivisorsEnding3.Dispose();
HeuristicCombinedDivisorSquaresEnding3.Dispose();
HeuristicCombinedDivisorsEnding7.Dispose();
HeuristicCombinedDivisorSquaresEnding7.Dispose();
HeuristicCombinedDivisorsEnding9.Dispose();
HeuristicCombinedDivisorSquaresEnding9.Dispose();
}
}
@@ -0,0 +1,45 @@
using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu.Accelerators;
public readonly struct HeuristicCombinedGpuViews(
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding1,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding3,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding7,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding9,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorSquaresEnding1,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorSquaresEnding3,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorSquaresEnding7,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorSquaresEnding9)
{
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding1 = combinedDivisorsEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding3 = combinedDivisorsEnding3;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding7 = combinedDivisorsEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding9 = combinedDivisorsEnding9;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding1 = combinedDivisorSquaresEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding3 = combinedDivisorSquaresEnding3;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding7 = combinedDivisorSquaresEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding9 = combinedDivisorSquaresEnding9;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectDivisors(byte ending) => ending switch
{
1 => CombinedDivisorsEnding1,
3 => CombinedDivisorsEnding3,
7 => CombinedDivisorsEnding7,
9 => CombinedDivisorsEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectDivisorSquares(byte ending) => ending switch
{
1 => CombinedDivisorSquaresEnding1,
3 => CombinedDivisorSquaresEnding3,
7 => CombinedDivisorSquaresEnding7,
9 => CombinedDivisorSquaresEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
@@ -0,0 +1,79 @@
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu.Accelerators;
internal sealed class HeuristicGroupABGpuTables
{
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
private static readonly HeuristicGroupABGpuTables[] _sharedTables = new HeuristicGroupABGpuTables[PerfectNumberConstants.RollingAccelerators];
private static readonly SemaphoreSlim[] _locks = [..Enumerable.Range(1, PerfectNumberConstants.RollingAccelerators).Select(_ => new SemaphoreSlim(1))];
internal static HeuristicGroupABGpuTables EnsureStaticTables(int acceleratorIndex, AcceleratorStream stream)
{
var @lock = _locks[acceleratorIndex];
@lock.Wait();
if (_sharedTables[acceleratorIndex] is {} existing)
{
@lock.Release();
return existing;
}
var accelerator = _accelerators[acceleratorIndex];
existing = new(accelerator, stream);
_sharedTables[acceleratorIndex] = existing;
@lock.Release();
return existing;
}
internal HeuristicGroupABGpuTables(Accelerator accelerator, AcceleratorStream stream)
{
var heuristicGroupA = HeuristicPrimeSieves.GroupADivisorsStorage;
HeuristicGroupADivisors = accelerator.CopySpanToDevice(stream, heuristicGroupA);
var heuristicGroupASquares = HeuristicPrimeSieves.GroupADivisorSquaresStorage;
HeuristicGroupADivisorSquares = accelerator.CopySpanToDevice(stream, heuristicGroupASquares);
HeuristicGroupBDivisorsEnding1 = accelerator.CopyUintSpanToDevice(stream, DivisorGenerator.SmallPrimesLastOneWithoutLastThree);
HeuristicGroupBDivisorSquaresEnding1 = accelerator.CopySpanToDevice(stream, DivisorGenerator.SmallPrimesPow2LastOneWithoutLastThree);
HeuristicGroupBDivisorsEnding7 = accelerator.CopyUintSpanToDevice(stream, DivisorGenerator.SmallPrimesLastSevenWithoutLastThree);
HeuristicGroupBDivisorSquaresEnding7 = accelerator.CopySpanToDevice(stream, DivisorGenerator.SmallPrimesPow2LastSevenWithoutLastThree);
HeuristicGroupBDivisorsEnding9 = accelerator.CopyUintSpanToDevice(stream, DivisorGenerator.SmallPrimesLastNineWithoutLastThree);
HeuristicGroupBDivisorSquaresEnding9 = accelerator.CopySpanToDevice(stream, DivisorGenerator.SmallPrimesPow2LastNineWithoutLastThree);
}
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisors;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisorSquares;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding1;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding1;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding7;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding7;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding9;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding9;
internal HeuristicGroupABGpuViews CreateViews() => new(
HeuristicGroupADivisors.View,
HeuristicGroupADivisorSquares.View,
HeuristicGroupBDivisorsEnding1.View,
HeuristicGroupBDivisorSquaresEnding1.View,
HeuristicGroupBDivisorsEnding7.View,
HeuristicGroupBDivisorSquaresEnding7.View,
HeuristicGroupBDivisorsEnding9.View,
HeuristicGroupBDivisorSquaresEnding9.View);
internal void Dispose()
{
HeuristicGroupADivisors.Dispose();
HeuristicGroupADivisorSquares.Dispose();
HeuristicGroupBDivisorsEnding1.Dispose();
HeuristicGroupBDivisorSquaresEnding1.Dispose();
HeuristicGroupBDivisorsEnding7.Dispose();
HeuristicGroupBDivisorSquaresEnding7.Dispose();
HeuristicGroupBDivisorsEnding9.Dispose();
HeuristicGroupBDivisorSquaresEnding9.Dispose();
}
}
@@ -0,0 +1,63 @@
using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu.Accelerators;
public readonly struct HeuristicGroupABGpuViews(
ArrayView1D<ulong, Stride1D.Dense> groupADivisors,
ArrayView1D<ulong, Stride1D.Dense> groupADivisorSquares,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding1,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorSquaresEnding1,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding7,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorSquaresEnding7,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding9,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorSquaresEnding9)
{
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupADivisors = groupADivisors;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupADivisorSquares = groupADivisorSquares;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding1 = groupBDivisorsEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorSquaresEnding1 = groupBDivisorSquaresEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding7 = groupBDivisorsEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorSquaresEnding7 = groupBDivisorSquaresEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding9 = groupBDivisorsEnding9;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorSquaresEnding9 = groupBDivisorSquaresEnding9;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectGroupB(byte ending) => ending switch
{
1 => GroupBDivisorsEnding1,
7 => GroupBDivisorsEnding7,
9 => GroupBDivisorsEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectGroupBSquares(byte ending) => ending switch
{
1 => GroupBDivisorSquaresEnding1,
7 => GroupBDivisorSquaresEnding7,
9 => GroupBDivisorSquaresEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectDivisors(HeuristicGpuDivisorTableKind kind) => kind switch
{
HeuristicGpuDivisorTableKind.GroupA => GroupADivisors,
HeuristicGpuDivisorTableKind.GroupBEnding1 => GroupBDivisorsEnding1,
HeuristicGpuDivisorTableKind.GroupBEnding7 => GroupBDivisorsEnding7,
HeuristicGpuDivisorTableKind.GroupBEnding9 => GroupBDivisorsEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectDivisorSquares(HeuristicGpuDivisorTableKind kind) => kind switch
{
HeuristicGpuDivisorTableKind.GroupA => GroupADivisorSquares,
HeuristicGpuDivisorTableKind.GroupBEnding1 => GroupBDivisorSquaresEnding1,
HeuristicGpuDivisorTableKind.GroupBEnding7 => GroupBDivisorSquaresEnding7,
HeuristicGpuDivisorTableKind.GroupBEnding9 => GroupBDivisorSquaresEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
@@ -0,0 +1,174 @@
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu.Accelerators;
internal sealed class HeuristicGroupABPrimeTesterAccelerator
{
#region Pool
[ThreadStatic]
private static Queue<HeuristicGroupABPrimeTesterAccelerator>? Pool;
internal static void WarmUp()
{
Accelerator[]? accelerators = AcceleratorPool.Shared.Accelerators;
int acceleratorCount = accelerators.Length;
for (var i = 0; i < acceleratorCount; i++)
{
Console.WriteLine($"Preparing heuristic accelerator {i}...");
var accelerator = accelerators[i];
AcceleratorStream stream = accelerator.CreateStream();
_ = HeuristicGroupABGpuTables.EnsureStaticTables(i, stream);
// _ = GpuKernelPool.GetOrAddKernels(accelerator, stream, KernelType.None);
// KernelContainer kernels = GpuKernelPool.GetOrAddKernels(accelerator, stream);
// GpuStaticTableInitializer.EnsureStaticTables(accelerator, kernels, stream);
stream.Synchronize();
stream.Dispose();
Console.WriteLine($"Heuristic accelerator {i} is ready");
}
}
internal static HeuristicGroupABPrimeTesterAccelerator Rent(int minBufferCapacity = 1)
{
var pool = Pool ??= new();
if (!pool.TryDequeue(out var lease))
{
lease = new HeuristicGroupABPrimeTesterAccelerator(minBufferCapacity);
}
else
{
lease.EnsureCapacity(minBufferCapacity);
}
return lease;
}
internal static void Return(HeuristicGroupABPrimeTesterAccelerator lease)
{
var pool = Pool ??= new();
pool.Enqueue(lease);
}
internal static void DisposeAll()
{
if (Pool != null)
{
while (Pool.TryDequeue(out var lease))
{
lease.DisposeResources();
}
}
}
internal static void Clear()
{
if (Pool != null)
{
var retained = new List<HeuristicGroupABPrimeTesterAccelerator>();
while (Pool.TryDequeue(out var lease))
{
lease.DisposeResources();
}
}
}
#endregion
public readonly HeuristicGroupABGpuViews DivisorTables;
internal readonly Context Context;
public readonly Accelerator Accelerator;
[ThreadStatic]
public static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>>? SmallPrimeSieveKernel;
internal static Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>> GetSmallPrimeSieveKernel(Accelerator accelerator)
{
var pool = SmallPrimeSieveKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
cached = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterKernels.SmallPrimeSieveKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>>();
pool[accelerator] = cached;
return cached;
}
public readonly Kernel HeuristicTrialDivisionKernel;
public MemoryBuffer1D<ulong, Stride1D.Dense>? Input = null;
public MemoryBuffer1D<byte, Stride1D.Dense>? Output = null;
public MemoryBuffer1D<int, Stride1D.Dense>? HeuristicFlag = null!;
public int BufferCapacity;
private HeuristicGroupABGpuTables _sharedTables;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisors => _sharedTables.HeuristicGroupADivisors;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisorSquares => _sharedTables.HeuristicGroupADivisorSquares;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding1 => _sharedTables.HeuristicGroupBDivisorsEnding1;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding1 => _sharedTables.HeuristicGroupBDivisorSquaresEnding1;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding7 => _sharedTables.HeuristicGroupBDivisorsEnding7;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding7 => _sharedTables.HeuristicGroupBDivisorSquaresEnding7;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding9 => _sharedTables.HeuristicGroupBDivisorsEnding9;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding9 => _sharedTables.HeuristicGroupBDivisorSquaresEnding9;
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
internal readonly int AcceleratorIndex;
internal HeuristicGroupABPrimeTesterAccelerator(int minBufferCapacity)
{
var acceleratorIndex = AcceleratorPool.Shared.Rent();
AcceleratorIndex = acceleratorIndex;
Accelerator accelerator = _accelerators[acceleratorIndex];
Context = accelerator.Context;
Accelerator = accelerator;
EnsureCapacity(minBufferCapacity);
HeuristicTrialDivisionKernel = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<int>, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGroupABGpuViews>(PrimeTesterKernels.HeuristicTrialGroupABDivisionKernel));
AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex);
// GpuStaticTableInitializer.EnsureStaticTables(accelerator, kernels, stream);
_sharedTables = HeuristicGroupABGpuTables.EnsureStaticTables(acceleratorIndex, stream);
DivisorTables = _sharedTables.CreateViews();
stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
}
public void EnsureCapacity(int minCapacity)
{
if (minCapacity <= BufferCapacity)
{
return;
}
Input?.Dispose();
Output?.Dispose();
HeuristicFlag?.Dispose();
Input = Accelerator.Allocate1D<ulong>(minCapacity);
Output = Accelerator.Allocate1D<byte>(minCapacity);
HeuristicFlag = Accelerator.Allocate1D<int>(minCapacity);
BufferCapacity = minCapacity;
}
public void Dispose()
{
Return(this);
}
internal void DisposeResources()
{
Input?.Dispose();
Output?.Dispose();
HeuristicFlag?.Dispose();
// ReleaseSharedTables(_sharedTables);
// These resources are shared between GPU leases
// Accelerator.Dispose();
// AcceleratorContext.Dispose();
}
}
@@ -0,0 +1,58 @@
using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu.Accelerators;
internal readonly struct LastDigitGpuTables
{
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
private static readonly LastDigitGpuTables[] _sharedTables = new LastDigitGpuTables[PerfectNumberConstants.RollingAccelerators];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static LastDigitGpuTables GetStaticTables(int acceleratorIndex) => _sharedTables[acceleratorIndex];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void WarmUp(int acceleratorIndex, AcceleratorStream stream) => _sharedTables[acceleratorIndex] = new LastDigitGpuTables(_accelerators[acceleratorIndex], stream);
internal LastDigitGpuTables(Accelerator accelerator, AcceleratorStream stream)
{
var primesLastOne = DivisorGenerator.SmallPrimesLastOne;
var primesLastSeven = DivisorGenerator.SmallPrimesLastSeven;
var primesLastThree = DivisorGenerator.SmallPrimesLastThree;
var primesLastNine = DivisorGenerator.SmallPrimesLastNine;
var primesPow2LastOne = DivisorGenerator.SmallPrimesPow2LastOne;
var primesPow2LastSeven = DivisorGenerator.SmallPrimesPow2LastSeven;
var primesPow2LastThree = DivisorGenerator.SmallPrimesPow2LastThree;
var primesPow2LastNine = DivisorGenerator.SmallPrimesPow2LastNine;
DevicePrimesLastOne = accelerator.Allocate1D(stream, primesLastOne);
DevicePrimesLastSeven = accelerator.Allocate1D(stream, primesLastSeven);
DevicePrimesLastThree = accelerator.Allocate1D(stream, primesLastThree);
DevicePrimesLastNine = accelerator.Allocate1D(stream, primesLastNine);
DevicePrimesPow2LastOne = accelerator.Allocate1D(stream, primesPow2LastOne);
DevicePrimesPow2LastSeven = accelerator.Allocate1D(stream, primesPow2LastSeven);
DevicePrimesPow2LastThree = accelerator.Allocate1D(stream, primesPow2LastThree);
DevicePrimesPow2LastNine = accelerator.Allocate1D(stream, primesPow2LastNine);
}
internal readonly MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastOne;
internal readonly MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastSeven;
internal readonly MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastThree;
internal readonly MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastNine;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastOne;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastSeven;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastThree;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastNine;
internal void Dispose()
{
DevicePrimesLastOne.Dispose();
DevicePrimesLastSeven.Dispose();
DevicePrimesLastThree.Dispose();
DevicePrimesLastNine.Dispose();
DevicePrimesPow2LastOne.Dispose();
DevicePrimesPow2LastSeven.Dispose();
DevicePrimesPow2LastThree.Dispose();
DevicePrimesPow2LastNine.Dispose();
}
}
@@ -0,0 +1,300 @@
using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu.Kernels;
namespace PerfectNumbers.Core.Gpu.Accelerators;
public sealed class PrimeOrderCalculatorAccelerator
{
#region Pool
[ThreadStatic]
private static Queue<PrimeOrderCalculatorAccelerator>? _pool;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PrimeOrderCalculatorAccelerator Rent(int primeTesterCapacity)
{
var pool = _pool ??= [];
if (!pool.TryDequeue(out var gpu))
{
int acceleratorIndex = AcceleratorPool.Shared.Rent();
gpu = new(acceleratorIndex, PerfectNumberConstants.DefaultFactorsBuffer, primeTesterCapacity, PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount, PerfectNumberConstants.DefaultSpecialMaxFactorCapacity);
}
else
{
gpu.EnsureCapacity(PerfectNumberConstants.DefaultFactorsBuffer, primeTesterCapacity);
}
return gpu;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Return(PrimeOrderCalculatorAccelerator gpu) => _pool!.Enqueue(gpu);
internal static void Clear()
{
Queue<PrimeOrderCalculatorAccelerator>? pool = _pool;
if (pool != null)
{
while (pool.TryDequeue(out var gpu))
{
gpu.Dispose();
}
}
}
internal static void DisposeAll()
{
Queue<PrimeOrderCalculatorAccelerator>? pool = _pool;
if (pool != null)
{
while (pool.TryDequeue(out var gpu))
{
gpu.Dispose();
}
}
}
internal static void WarmUp()
{
Accelerator[] accelerators = AcceleratorPool.Shared.Accelerators;
int acceleratorCount = accelerators.Length;
for (var i = 0; i < acceleratorCount; i++)
{
Console.WriteLine($"Preparing accelerator {i}...");
var accelerator = accelerators[i];
AcceleratorStreamPool.WarmUp(i);
// Don't take this from the pool as quick uploads of data to the accelerator consumes much of GPU's memory and throws.
AcceleratorStream stream = accelerator.CreateStream();
// TODO: Review which tables are needed when final execution path is defined.
LastDigitGpuTables.WarmUp(i, stream);
HeuristicCombinedGpuTables.WarmUp(i, stream);
// SmallPrimeFactorGpuTables.WarmUp(i, stream);
// SharedHeuristicGpuTables.EnsureStaticTables(accelerator, stream);
// _ = GpuKernelPool.GetOrAddKernels(accelerator, stream, KernelType.None);
// KernelContainer kernels = GpuKernelPool.GetOrAddKernels(accelerator, stream);
// GpuStaticTableInitializer.EnsureStaticTables(accelerator, kernels, stream);
stream.Synchronize();
stream.Dispose();
accelerator.Synchronize();
}
}
#endregion
#region Static Tables
public readonly ArrayView1D<uint, Stride1D.Dense> DevicePrimesLastOne;
public readonly ArrayView1D<uint, Stride1D.Dense> DevicePrimesLastSeven;
public readonly ArrayView1D<uint, Stride1D.Dense> DevicePrimesLastThree;
public readonly ArrayView1D<uint, Stride1D.Dense> DevicePrimesLastNine;
public readonly ArrayView1D<ulong, Stride1D.Dense> DevicePrimesPow2LastOne;
public readonly ArrayView1D<ulong, Stride1D.Dense> DevicePrimesPow2LastSeven;
public readonly ArrayView1D<ulong, Stride1D.Dense> DevicePrimesPow2LastThree;
public readonly ArrayView1D<ulong, Stride1D.Dense> DevicePrimesPow2LastNine;
public readonly HeuristicCombinedGpuViews DivisorTables;
public readonly ArrayView1D<uint, Stride1D.Dense> SmallPrimeFactorPrimes;
public readonly ArrayView1D<ulong, Stride1D.Dense> SmallPrimeFactorSquares;
#endregion
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
public readonly Accelerator Accelerator;
public readonly int AcceleratorIndex;
public MemoryBuffer1D<ulong, Stride1D.Dense> Input;
public MemoryBuffer1D<ulong, Stride1D.Dense> OutputUlong;
public MemoryBuffer1D<byte, Stride1D.Dense> OutputByte;
public MemoryBuffer1D<int, Stride1D.Dense> OutputInt;
public MemoryBuffer1D<KeyValuePair<ulong, int>, Stride1D.Dense> Pow2ModEntriesToTestOnDevice;
public readonly MemoryBuffer1D<int, Stride1D.Dense> SmallPrimeFactorCountSlot;
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> SpecialMaxResult;
public Dictionary<ulong, int> Pow2ModEntriesToTestOnHost = [];
public readonly Action<AcceleratorStream, int, ulong, ArrayView1D<KeyValuePair<ulong, int>, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> CheckFactorsKernelLauncher;
public readonly Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> ConvertToStandardKernelLauncher;
public readonly Action<AcceleratorStream, Index1D, byte, ArrayView<int>, ulong, ulong, HeuristicCombinedGpuViews> HeuristicCombinedTrialDivisionKernelLauncher;
public readonly Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> KeepMontgomeryKernelLauncher;
public readonly Action<AcceleratorStream, ulong, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> PollardRhoKernelLauncher;
public readonly Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>> Pow2ModWideKernelLauncher;
public readonly Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<byte>> SharesFactorKernelLauncher;
public readonly Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> SmallPrimeFactorKernelLauncher;
public readonly Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>> SmallPrimeSieveKernelLauncher;
public readonly Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> SpecialMaxKernelLauncher;
public void EnsureCapacity(int factorsCount, int primeTesterCapacity)
{
Accelerator accelerator = Accelerator;
MemoryBuffer1D<KeyValuePair<ulong, int>, Stride1D.Dense> pow2ModEntriesToTestOnDevice = Pow2ModEntriesToTestOnDevice;
if (pow2ModEntriesToTestOnDevice.Length < factorsCount)
{
lock (accelerator)
{
pow2ModEntriesToTestOnDevice.Dispose();
Pow2ModEntriesToTestOnDevice = accelerator.Allocate1D<KeyValuePair<ulong, int>>(factorsCount);
}
}
MemoryBuffer1D<ulong, Stride1D.Dense> input = Input;
if (input.Length < primeTesterCapacity)
{
lock (accelerator)
{
input.Dispose();
Input = accelerator.Allocate1D<ulong>(primeTesterCapacity);
}
}
MemoryBuffer1D<byte, Stride1D.Dense> outputByte = OutputByte;
if (outputByte.Length < primeTesterCapacity)
{
lock (accelerator)
{
outputByte.Dispose();
OutputByte = accelerator.Allocate1D<byte>(primeTesterCapacity);
}
}
MemoryBuffer1D<int, Stride1D.Dense> outputInt = OutputInt;
if (outputInt.Length < primeTesterCapacity)
{
lock (accelerator)
{
outputInt.Dispose();
OutputInt = accelerator.Allocate1D<int>(primeTesterCapacity);
}
}
}
public void EnsureSmallPrimeFactorSlotsCapacity(int newSize)
{
// Console.WriteLine($"Resizing GPU scratch buffer from pool ({buffer.SmallPrimeFactorPrimeSlots.Length} / {smallPrimeFactorSlotCount}), ({buffer.SpecialMaxFactors.Length}/{specialMaxFactorCapacity})");
var accelerator = Accelerator;
MemoryBuffer1D<ulong, Stride1D.Dense> outputUlong = OutputUlong;
if (outputUlong.Length < newSize)
{
lock (accelerator)
{
outputUlong.Dispose();
OutputUlong = accelerator.Allocate1D<ulong>(newSize);
}
}
MemoryBuffer1D<int, Stride1D.Dense> outputInt = OutputInt;
if (outputInt.Length < newSize)
{
lock (accelerator)
{
outputInt.Dispose();
OutputInt = accelerator.Allocate1D<int>(newSize);
}
}
}
public void EnsureSpecialMaxFactorsCapacity(int newSize)
{
var accelerator = Accelerator;
MemoryBuffer1D<ulong, Stride1D.Dense> input = Input;
if (input.Length < newSize)
{
lock (accelerator)
{
input.Dispose();
Input = accelerator.Allocate1D<ulong>(newSize);
}
}
MemoryBuffer1D<ulong, Stride1D.Dense> outputUlong = OutputUlong;
if (outputUlong.Length < newSize)
{
lock (accelerator)
{
outputUlong.Dispose();
OutputUlong = accelerator.Allocate1D<ulong>(newSize);
}
}
}
public PrimeOrderCalculatorAccelerator(int acceleratorIndex, int factorsCount, int primeTesterCapacity, int smallPrimeFactorSlotCount, int specialMaxFactorCapacity)
{
var accelerator = _accelerators[acceleratorIndex];
Accelerator = accelerator;
AcceleratorIndex = acceleratorIndex;
lock (accelerator)
{
Input = accelerator.Allocate1D<ulong>(Math.Max(specialMaxFactorCapacity, primeTesterCapacity));
OutputByte = Accelerator.Allocate1D<byte>(primeTesterCapacity);
OutputInt = Accelerator.Allocate1D<int>(Math.Max(primeTesterCapacity, smallPrimeFactorSlotCount));
OutputUlong = accelerator.Allocate1D<ulong>(Math.Max(specialMaxFactorCapacity, smallPrimeFactorSlotCount));
Pow2ModEntriesToTestOnDevice = accelerator.Allocate1D<KeyValuePair<ulong, int>>(factorsCount);
SmallPrimeFactorCountSlot = accelerator.Allocate1D<int>(1);
SpecialMaxResult = accelerator.Allocate1D<ulong>(1);
LastDigitGpuTables lastDigitSharedTables = LastDigitGpuTables.GetStaticTables(acceleratorIndex);
DevicePrimesLastOne = lastDigitSharedTables.DevicePrimesLastOne.View;
DevicePrimesLastSeven = lastDigitSharedTables.DevicePrimesLastSeven.View;
DevicePrimesLastThree = lastDigitSharedTables.DevicePrimesLastThree.View;
DevicePrimesLastNine = lastDigitSharedTables.DevicePrimesLastNine.View;
DevicePrimesPow2LastOne = lastDigitSharedTables.DevicePrimesPow2LastOne.View;
DevicePrimesPow2LastSeven = lastDigitSharedTables.DevicePrimesPow2LastSeven.View;
DevicePrimesPow2LastThree = lastDigitSharedTables.DevicePrimesPow2LastThree.View;
DevicePrimesPow2LastNine = lastDigitSharedTables.DevicePrimesPow2LastNine.View;
DivisorTables = HeuristicCombinedGpuTables
.GetStaticTables(acceleratorIndex)
.CreateViews();
var smallPrimeFactorTables = SmallPrimeFactorGpuTables.GetStaticTables(acceleratorIndex);
if (smallPrimeFactorTables.HasValue)
{
SmallPrimeFactorGpuTables tables = smallPrimeFactorTables.Value;
SmallPrimeFactorPrimes = tables.Primes.View;
SmallPrimeFactorSquares = tables.Squares.View;
}
CheckFactorsKernelLauncher = KernelUtil.GetKernel(accelerator.LoadStreamKernel<int, ulong, ArrayView1D<KeyValuePair<ulong, int>, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>(PrimeOrderGpuHeuristics.CheckFactorsKernel)).CreateLauncherDelegate<Action<AcceleratorStream, int, ulong, ArrayView1D<KeyValuePair<ulong, int>, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>>();
ConvertToStandardKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>(Pow2MontgomeryKernels.Pow2MontgomeryKernelConvertToStandard)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>>();
HeuristicCombinedTrialDivisionKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, byte, ArrayView<int>, ulong, ulong, HeuristicCombinedGpuViews>(PrimeTesterKernels.HeuristicTrialCombinedDivisionKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, byte, ArrayView<int>, ulong, ulong, HeuristicCombinedGpuViews>>();
KeepMontgomeryKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>(Pow2MontgomeryKernels.Pow2MontgomeryKernelKeepMontgomery)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>>();
PollardRhoKernelLauncher = KernelUtil.GetKernel(accelerator.LoadStreamKernel<ulong, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2MontgomeryKernels.TryPollardRhoKernel)).CreateLauncherDelegate<Action<AcceleratorStream, ulong, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
Pow2ModWideKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>>(PrimeOrderGpuHeuristics.Pow2ModKernelWide)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>>>();
SharesFactorKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterKernels.SharesFactorKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<byte>>>();
SmallPrimeFactorKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(SmallPrimeFactorKernels.SmallPrimeFactorKernelScan)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
SmallPrimeSieveKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterKernels.SmallPrimeSieveKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>>();
SpecialMaxKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(PrimeOrderGpuHeuristics.EvaluateSpecialMaxCandidatesKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
}
}
public void Dispose()
{
Input.Dispose();
OutputByte.Dispose();
OutputInt.Dispose();
OutputUlong.Dispose();
Pow2ModEntriesToTestOnDevice.Dispose();
SmallPrimeFactorCountSlot.Dispose();
SpecialMaxResult.Dispose();
// These resources are shared between GPU leases
// Stream.Dispose();
// Accelerator.Dispose();
// Context.Dispose();
}
}
@@ -0,0 +1,75 @@
using System.Runtime.CompilerServices;
using ILGPU;
namespace PerfectNumbers.Core.Gpu;
public static class ThreadStaticDeterministicRandomGpu
{
[ThreadStatic]
private static DeterministicRandomGpu? _exclusive;
internal static DeterministicRandomGpu Exclusive => _exclusive ?? new DeterministicRandomGpu();
}
internal struct DeterministicRandomGpu
{
private static long s_seedCounter;
private ulong _state;
public readonly ulong State
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _state;
}
}
public DeterministicRandomGpu()
{
_state = CreateThreadState();
}
public DeterministicRandomGpu(ulong initialState)
{
_state = initialState;
}
public ulong NextUInt64()
{
ulong state = _state;
state ^= state >> 12;
state ^= state << 25;
state ^= state >> 27;
_state = state;
return state * 2685821657736338717UL;
}
public GpuUInt128 NextUInt128()
{
GpuUInt128 high = (GpuUInt128)NextUInt64();
ReadOnlyGpuUInt128 low = (ReadOnlyGpuUInt128)NextUInt64();
high.ShiftLeft(64);
high.Add(low);
return high;
}
public void SetState(ulong state)
{
_state = state;
}
private static ulong CreateThreadState()
{
ulong seed = (ulong)Atomic.Add(ref s_seedCounter, 1);
seed += 0x9E3779B97F4A7C15UL;
seed = (seed ^ (seed >> 30)) * 0xBF58476D1CE4E5B9UL;
seed = (seed ^ (seed >> 27)) * 0x94D049BB133111EBUL;
seed ^= seed >> 31;
if (seed == 0UL)
{
seed = 0x9E3779B97F4A7C15UL;
}
return seed;
}
}
@@ -1,5 +1,4 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using PerfectNumbers.Core;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -25,10 +24,7 @@ internal struct ExponentRemainderStepperGpu
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MatchesDivisor(in GpuDivisorPartialData divisor) public readonly bool MatchesDivisor(in GpuDivisorPartialData divisor) => _modulus == divisor.Modulus;
{
return _modulus == divisor.Modulus;
}
/// <summary> /// <summary>
/// Clears the cached residue so the stepper can be reused for another divisor. /// Clears the cached residue so the stepper can be reused for another divisor.
-163
View File
@@ -1,163 +0,0 @@
using System.Collections.Concurrent;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
public static class GpuContextPool
{
private static readonly bool PoolingEnabled = true;
// The pool intentionally skips pre-loading ProcessEightBitWindows kernels so accelerator initialization
// stays stable; GpuKernelPool JIT-compiles them on first use alongside the small-cycle uploads.
// Default device preference for generic GPU kernels (prime scans, NTT, etc.)
public static bool ForceCpu { get; set; } = false;
internal sealed class PooledContext
{
private bool disposed;
public Context Context { get; }
public Accelerator Accelerator { get; }
public bool IsCpu { get; }
public object ExecutionLock { get; } = new();
public PooledContext(bool preferCpu)
{
Context = Context.CreateDefault();
Accelerator = Context.GetPreferredDevice(preferCpu).CreateAccelerator(Context);
IsCpu = Accelerator.AcceleratorType == AcceleratorType.CPU;
// NOTE: Avoid loading/compiling any kernel here to prevent implicit
// CL stream/queue creation during accelerator construction.
// Some OpenCL drivers are fragile when a queue is created immediately
// after device init. Real kernels will be JIT-loaded at first use.
}
public void Dispose()
{
if (disposed)
{
return;
}
// Ensure all cached GPU buffers for this accelerator are released.
NttGpuMath.ClearCaches(Accelerator);
// Release PrimeTester GPU state for this accelerator (kernel/device primes)
// Clear any per-accelerator cached resources to avoid releasing
// after the accelerator is destroyed.
PrimeTester.ClearGpuCaches(Accelerator);
Accelerator.Dispose();
Context.Dispose();
disposed = true;
}
}
private static readonly ConcurrentQueue<PooledContext> CpuPool = new();
private static readonly ConcurrentQueue<PooledContext> GpuPool = new();
private static readonly object CreationLock = new();
public static GpuContextLease Rent()
{
return RentPreferred(ForceCpu);
}
// Allows callers to choose CPU/GPU per use-case, decoupled from ForceCpu.
public static GpuContextLease RentPreferred(bool preferCpu)
{
if (PoolingEnabled)
{
if (preferCpu && CpuPool.TryDequeue(out var cpu))
{
return new GpuContextLease(cpu, CreationLock);
}
if (!preferCpu && GpuPool.TryDequeue(out var gpu))
{
return new GpuContextLease(gpu, CreationLock);
}
}
// Serialize accelerator creation to avoid concurrent CL device/stream setup across threads.
lock (CreationLock)
{
return new GpuContextLease(new PooledContext(preferCpu), CreationLock);
}
}
public static void DisposeAll()
{
if (!PoolingEnabled)
{
return;
}
while (CpuPool.TryDequeue(out var cpu))
{
cpu.Dispose();
}
while (GpuPool.TryDequeue(out var gpu))
{
gpu.Dispose();
}
PrimeTester.DisposeGpuContexts();
}
private static void Return(PooledContext ctx)
{
if (PoolingEnabled)
{
ctx.Accelerator.Synchronize();
if (ctx.IsCpu)
{
CpuPool.Enqueue(ctx);
}
else
{
GpuPool.Enqueue(ctx);
}
}
else
{
lock (CreationLock)
{
ctx.Dispose();
}
}
}
public struct GpuContextLease
{
private readonly PooledContext _ctx;
private bool disposed;
internal GpuContextLease(PooledContext ctx, object kernelInitLock)
{
_ctx = ctx;
KernelInitLock = kernelInitLock;
}
public Context Context => _ctx.Context;
public Accelerator Accelerator => _ctx.Accelerator;
public object KernelInitLock { get; }
public object ExecutionLock => _ctx.ExecutionLock;
public void Dispose()
{
if (disposed)
{
return;
}
Return(_ctx);
disposed = true;
}
}
}
-213
View File
@@ -1,213 +0,0 @@
using System.Collections.Concurrent;
using ILGPU;
using ILGPU.Runtime;
using static PerfectNumbers.Core.Gpu.GpuContextPool;
namespace PerfectNumbers.Core.Gpu;
public sealed class GpuKernelLease
{
private static readonly ConcurrentQueue<GpuKernelLease> Pool = new();
private IDisposable? _limiter;
private GpuContextLease? _gpu;
private KernelContainer? _kernels;
private AcceleratorStream? _stream;
private object? _executionLock;
private bool _disposed;
private GpuKernelLease()
{
}
internal static GpuKernelLease Rent(IDisposable limiter, GpuContextLease gpu, KernelContainer kernels)
{
if (!Pool.TryDequeue(out var lease))
{
lease = new GpuKernelLease();
}
lease._limiter = limiter;
lease._gpu = gpu;
lease._kernels = kernels;
lease._stream = null;
lease._executionLock = gpu.ExecutionLock;
lease._disposed = false;
return lease;
}
public Accelerator Accelerator => _gpu?.Accelerator ?? throw new NullReferenceException("GPU context is already released");
public AcceleratorStream Stream
{
get
{
if (_stream is { } stream)
{
return stream;
}
// It feels that we can combine these into 1 return statement, but that increases the max stack frame size to 3 from 2
stream = Accelerator.CreateStream();
_stream = stream;
return stream;
}
}
public ExecutionScope EnterExecutionScope() => new ExecutionScope(_executionLock);
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>> OrderKernel
{
get
{
var accel = Accelerator; // avoid capturing 'this' in lambda
lock (_gpu!.Value.KernelInitLock)
{
return KernelContainer.InitOnce(ref _kernels!.Order, () =>
{
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernels.OrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>>();
});
}
}
}
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModKernel
{
get
{
var accel = Accelerator;
lock (_gpu!.Value.KernelInitLock)
{
return KernelContainer.InitOnce(ref _kernels!.Pow2Mod, () =>
{
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
});
}
}
}
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalKernel
{
get
{
var accel = Accelerator;
lock (_gpu!.Value.KernelInitLock)
{
return KernelContainer.InitOnce(ref _kernels!.Incremental, () =>
{
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>>();
});
}
}
}
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalOrderKernel
{
get
{
var accel = Accelerator;
lock (_gpu!.Value.KernelInitLock)
{
return KernelContainer.InitOnce(ref _kernels!.IncrementalOrder, () =>
{
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalOrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
});
}
}
}
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModOrderKernel
{
get
{
var accel = Accelerator;
lock (_gpu!.Value.KernelInitLock)
{
return KernelContainer.InitOnce(ref _kernels!.Pow2ModOrder, () =>
{
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModOrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
});
}
}
}
public Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> SmallPrimeFactorKernel
{
get
{
var accel = Accelerator;
lock (_gpu!.Value.KernelInitLock)
{
return KernelContainer.InitOnce(ref _kernels!.SmallPrimeFactor, () =>
{
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(SmallPrimeFactorKernels.SmallPrimeFactorKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
});
}
}
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
_stream?.Dispose();
_stream = null;
_gpu?.Dispose();
_gpu = null;
_executionLock = null;
_limiter?.Dispose();
_limiter = null;
}
_kernels = null;
_disposed = true;
Pool.Enqueue(this);
}
public void Dispose()
{
Dispose(disposing: false);
}
public readonly struct ExecutionScope
{
private readonly object? _lock;
public ExecutionScope(object? sync)
{
_lock = sync;
if (_lock is not null)
{
Monitor.Enter(_lock);
}
}
public void Dispose()
{
if (_lock is not null)
{
Monitor.Exit(_lock);
}
}
}
}
+180 -253
View File
@@ -1,188 +1,188 @@
using System.Collections.Concurrent;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using static PerfectNumbers.Core.Gpu.GpuContextPool; using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
public readonly struct ResidueAutomatonArgs
{
public readonly ulong Q0M10;
public readonly ulong Step10;
public readonly ulong Q0M8;
public readonly ulong Step8;
public readonly ulong Q0M3;
public readonly ulong Step3;
public readonly ulong Q0M5;
public readonly ulong Step5;
public ResidueAutomatonArgs(ulong q0m10, ulong step10, ulong q0m8, ulong step8, ulong q0m3, ulong step3, ulong q0m5, ulong step5)
{
Q0M10 = q0m10; Step10 = step10; Q0M8 = q0m8; Step8 = step8; Q0M3 = q0m3; Step3 = step3; Q0M5 = q0m5; Step5 = step5;
}
}
public sealed class KernelContainer
{
// Serializes first-time initialization of kernels/buffers per accelerator.
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>? Order;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>? Incremental;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>,
ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>,
ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2Mod;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? IncrementalOrder;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2ModOrder;
public Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? SmallPrimeFactor;
// Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length.
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallCycles;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimeFactorsPrimes;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorsSquares;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorPrimeSlots;
public MemoryBuffer1D<int, Stride1D.Dense>? SmallPrimeFactorExponentSlots;
public MemoryBuffer1D<int, Stride1D.Dense>? SmallPrimeFactorCountSlot;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorRemainingSlot;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastOne;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastOne;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastSeven;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastSeven;
public static T InitOnce<T>(ref T? slot, Func<T> factory) where T : class
{
var current = Volatile.Read(ref slot);
if (current is not null)
{
return current;
}
current = Volatile.Read(ref slot);
if (current is null)
{
current = factory();
Volatile.Write(ref slot, current);
}
return current;
}
}
public readonly struct ResiduePrimeViews(
ArrayView1D<uint, Stride1D.Dense> lastOne,
ArrayView1D<uint, Stride1D.Dense> lastSeven,
ArrayView1D<ulong, Stride1D.Dense> lastOnePow2,
ArrayView1D<ulong, Stride1D.Dense> lastSevenPow2)
{
public readonly ArrayView1D<uint, Stride1D.Dense> LastOne = lastOne;
public readonly ArrayView1D<uint, Stride1D.Dense> LastSeven = lastSeven;
public readonly ArrayView1D<ulong, Stride1D.Dense> LastOnePow2 = lastOnePow2;
public readonly ArrayView1D<ulong, Stride1D.Dense> LastSevenPow2 = lastSevenPow2;
}
public readonly struct SmallPrimeFactorTables
{
public readonly MemoryBuffer1D<uint, Stride1D.Dense> Primes;
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> Squares;
public readonly int Count;
public SmallPrimeFactorTables(
MemoryBuffer1D<uint, Stride1D.Dense> primes,
MemoryBuffer1D<ulong, Stride1D.Dense> squares,
int count)
{
Primes = primes;
Squares = squares;
Count = count;
}
public ArrayView1D<uint, Stride1D.Dense> PrimesView => Primes.View;
public ArrayView1D<ulong, Stride1D.Dense> SquaresView => Squares.View;
}
public readonly struct SmallPrimeFactorScratch
{
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> PrimeSlots;
public readonly MemoryBuffer1D<int, Stride1D.Dense> ExponentSlots;
public readonly MemoryBuffer1D<int, Stride1D.Dense> CountSlot;
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> RemainingSlot;
public SmallPrimeFactorScratch(
MemoryBuffer1D<ulong, Stride1D.Dense> primeSlots,
MemoryBuffer1D<int, Stride1D.Dense> exponentSlots,
MemoryBuffer1D<int, Stride1D.Dense> countSlot,
MemoryBuffer1D<ulong, Stride1D.Dense> remainingSlot)
{
PrimeSlots = primeSlots;
ExponentSlots = exponentSlots;
CountSlot = countSlot;
RemainingSlot = remainingSlot;
}
public void Clear()
{
PrimeSlots.MemSetToZero();
ExponentSlots.MemSetToZero();
CountSlot.MemSetToZero();
RemainingSlot.MemSetToZero();
}
}
public class GpuKernelPool public class GpuKernelPool
{ {
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. [ThreadStatic]
private static KernelContainer[]? _pool;
private static KernelContainer GetKernels(Accelerator accelerator) public static T InitOnce<T>(ref T? target, Func<T> valueFactory) where T : class
{ {
return KernelCache.GetOrAdd(accelerator, _ => new KernelContainer()); if (target is { } value)
{
return value;
} }
var newValue = valueFactory();
return Interlocked.CompareExchange(ref target, newValue, null) ?? newValue;
}
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
internal static KernelContainer GetOrAddKernels(int acceleratorIndex, AcceleratorStream stream, KernelType kernelType)
{
var pool = _pool ??= [];
if (pool[acceleratorIndex] is not { } kernels)
{
kernels = new();
}
Accelerator accelerator = _accelerators[acceleratorIndex];
if (kernelType.HasFlag(KernelType.OrderKernelScan))
{
InitOrderKernelScan(accelerator, kernels);
}
if (kernelType.HasFlag(KernelType.Pow2ModKernelScan))
{
InitPow2ModKernelScan(accelerator, kernels);
}
if (kernelType.HasFlag(KernelType.IncrementalKernelScan))
{
InitIncrementalKernelScan(accelerator, kernels);
}
if (kernelType.HasFlag(KernelType.IncrementalOrderKernelScan))
{
IncrementalOrderKernelScan(accelerator, kernels);
}
if (kernelType.HasFlag(KernelType.Pow2ModOrderKernelScan))
{
InitPow2ModOrderKernelScan(accelerator, kernels);
}
PreloadStaticTables(accelerator, kernels, stream, kernelType);
pool[acceleratorIndex] = kernels;
return kernels;
static void InitOrderKernelScan(Accelerator accelerator, KernelContainer kernels)
{
InitOnce(ref kernels.Order, () =>
{
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernels.OrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>>();
});
}
static void InitPow2ModKernelScan(Accelerator accelerator, KernelContainer kernels)
{
InitOnce(ref kernels.Pow2Mod, () =>
{
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
});
}
static void InitIncrementalKernelScan(Accelerator accelerator, KernelContainer kernels)
{
InitOnce(ref kernels.Incremental, () =>
{
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>>();
});
}
static void IncrementalOrderKernelScan(Accelerator accelerator, KernelContainer kernels)
{
InitOnce(ref kernels.IncrementalOrder, () =>
{
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalOrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte,
ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
});
}
static void InitPow2ModOrderKernelScan(Accelerator accelerator, KernelContainer kernels)
{
InitOnce(ref kernels.Pow2ModOrder, () =>
{
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModOrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
});
}
}
internal static void PreloadStaticTables(Accelerator accelerator, KernelContainer kernels, AcceleratorStream stream, KernelType kernelType)
{
if (kernelType.HasFlag(KernelType.SmallCycles))
{
EnsureSmallCyclesOnDevice(accelerator, kernels, stream);
}
if (kernelType.HasFlag(KernelType.SmallPrimes))
{
EnsureSmallPrimesOnDevice(accelerator, kernels, stream);
}
}
public static ArrayView1D<ulong, Stride1D.Dense> GetSmallCyclesOnDevice(KernelContainer kernels) => kernels.SmallCycles!;
// Ensures the small cycles table is uploaded to the device for the given accelerator. // Ensures the small cycles table is uploaded to the device for the given accelerator.
// Returns the ArrayView to pass into kernels (when kernels are extended to accept it). // Returns the ArrayView to pass into kernels (when kernels are extended to accept it).
public static ArrayView1D<ulong, Stride1D.Dense> EnsureSmallCyclesOnDevice(Accelerator accelerator) private static void EnsureSmallCyclesOnDevice(Accelerator accelerator, KernelContainer kernels, AcceleratorStream stream)
{ {
var kernels = GetKernels(accelerator); if (kernels.SmallCycles is { })
if (kernels.SmallCycles is { } buffer)
{ {
return buffer.View; return;
} }
// Ensure single upload per accelerator even if multiple threads race here. var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
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. MemoryBuffer1D<ulong, Stride1D.Dense>? device;
lock (accelerator)
{ {
if (kernels.SmallCycles is { } existing) device = accelerator.Allocate1D<ulong>(host.Length);
{
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. device.View.CopyFromCPU(stream, host);
var device = accelerator.Allocate1D<ulong>(host.Length);
device.View.CopyFromCPU(host);
kernels.SmallCycles = device; kernels.SmallCycles = device;
return device.View;
}
} }
public static ResiduePrimeViews EnsureSmallPrimesOnDevice(Accelerator accelerator) public static ResiduePrimeViews GetSmallPrimesOnDevice(KernelContainer kernels) => new(
kernels.SmallPrimesLastOne!,
kernels.SmallPrimesLastSeven!,
kernels.SmallPrimesPow2LastOne!,
kernels.SmallPrimesPow2LastSeven!
);
private static void EnsureSmallPrimesOnDevice(Accelerator accelerator, KernelContainer kernels, AcceleratorStream stream)
{ {
var kernels = GetKernels(accelerator); if (kernels.SmallPrimesLastOne is { } &&
if (kernels.SmallPrimesLastOne is { } lastOne && kernels.SmallPrimesLastSeven is { } &&
kernels.SmallPrimesLastSeven is { } lastSeven && kernels.SmallPrimesPow2LastOne is { } &&
kernels.SmallPrimesPow2LastOne is { } lastOnePow2 && kernels.SmallPrimesPow2LastSeven is { })
kernels.SmallPrimesPow2LastSeven is { } lastSevenPow2)
{ {
return new ResiduePrimeViews(lastOne.View, lastSeven.View, lastOnePow2.View, lastSevenPow2.View); return;
} }
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 { } &&
kernels.SmallPrimesLastSeven is { } &&
kernels.SmallPrimesPow2LastOne is { } &&
kernels.SmallPrimesPow2LastSeven is { })
{ {
if (kernels.SmallPrimesLastOne is { } existingLastOne && return;
kernels.SmallPrimesLastSeven is { } existingLastSeven &&
kernels.SmallPrimesPow2LastOne is { } existingLastOnePow2 &&
kernels.SmallPrimesPow2LastSeven is { } existingLastSevenPow2)
{
return new ResiduePrimeViews(existingLastOne.View, existingLastSeven.View, existingLastOnePow2.View, existingLastSevenPow2.View);
} }
var hostLastOne = PrimesGenerator.SmallPrimesLastOne; var hostLastOne = PrimesGenerator.SmallPrimesLastOne;
@@ -190,100 +190,27 @@ public class GpuKernelPool
var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne; var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne;
var hostLastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven; var hostLastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven;
var deviceLastOne = accelerator.Allocate1D<uint>(hostLastOne.Length); MemoryBuffer1D<uint, Stride1D.Dense>? deviceLastOne;
deviceLastOne.View.CopyFromCPU(hostLastOne); MemoryBuffer1D<uint, Stride1D.Dense>? deviceLastSeven;
var deviceLastSeven = accelerator.Allocate1D<uint>(hostLastSeven.Length); MemoryBuffer1D<ulong, Stride1D.Dense>? deviceLastOnePow2;
deviceLastSeven.View.CopyFromCPU(hostLastSeven); MemoryBuffer1D<ulong, Stride1D.Dense>? deviceLastSevenPow2;
var deviceLastOnePow2 = accelerator.Allocate1D<ulong>(hostLastOnePow2.Length);
deviceLastOnePow2.View.CopyFromCPU(hostLastOnePow2);
var deviceLastSevenPow2 = accelerator.Allocate1D<ulong>(hostLastSevenPow2.Length);
deviceLastSevenPow2.View.CopyFromCPU(hostLastSevenPow2);
lock (accelerator)
{
deviceLastOne = accelerator.Allocate1D<uint>(hostLastOne.Length);
deviceLastSeven = accelerator.Allocate1D<uint>(hostLastSeven.Length);
deviceLastOnePow2 = accelerator.Allocate1D<ulong>(hostLastOnePow2.Length);
deviceLastSevenPow2 = accelerator.Allocate1D<ulong>(hostLastSevenPow2.Length);
}
deviceLastOne.View.CopyFromCPU(stream, hostLastOne);
deviceLastSeven.View.CopyFromCPU(stream, hostLastSeven);
deviceLastOnePow2.View.CopyFromCPU(stream, hostLastOnePow2);
deviceLastSevenPow2.View.CopyFromCPU(stream, hostLastSevenPow2);
kernels.SmallPrimesLastOne = deviceLastOne; kernels.SmallPrimesLastOne = deviceLastOne;
kernels.SmallPrimesLastSeven = deviceLastSeven; kernels.SmallPrimesLastSeven = deviceLastSeven;
kernels.SmallPrimesPow2LastOne = deviceLastOnePow2; kernels.SmallPrimesPow2LastOne = deviceLastOnePow2;
kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2; kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2;
return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View);
}
}
public static SmallPrimeFactorTables EnsureSmallPrimeFactorTables(Accelerator accelerator)
{
var kernels = GetKernels(accelerator);
if (kernels.SmallPrimeFactorsPrimes is { } primeBuffer && kernels.SmallPrimeFactorsSquares is { } squareBuffer)
{
return new SmallPrimeFactorTables(primeBuffer, squareBuffer, (int)primeBuffer.Length);
}
lock (kernels)
{
if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares)
{
return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.Length);
}
var hostPrimes = PrimesGenerator.SmallPrimes;
var hostSquares = PrimesGenerator.SmallPrimesPow2;
var devicePrimes = accelerator.Allocate1D<uint>(hostPrimes.Length);
devicePrimes.View.CopyFromCPU(hostPrimes);
var deviceSquares = accelerator.Allocate1D<ulong>(hostSquares.Length);
deviceSquares.View.CopyFromCPU(hostSquares);
kernels.SmallPrimeFactorsPrimes = devicePrimes;
kernels.SmallPrimeFactorsSquares = deviceSquares;
return new SmallPrimeFactorTables(devicePrimes, deviceSquares, hostPrimes.Length);
}
}
public static SmallPrimeFactorScratch EnsureSmallPrimeFactorScratch(Accelerator accelerator, int slotCount)
{
var kernels = GetKernels(accelerator);
lock (kernels)
{
MemoryBuffer1D<ulong, Stride1D.Dense>? primeSlots = kernels.SmallPrimeFactorPrimeSlots;
if (primeSlots is null || primeSlots.Length < slotCount)
{
primeSlots?.Dispose();
primeSlots = accelerator.Allocate1D<ulong>(slotCount);
kernels.SmallPrimeFactorPrimeSlots = primeSlots;
}
MemoryBuffer1D<int, Stride1D.Dense>? exponentSlots = kernels.SmallPrimeFactorExponentSlots;
if (exponentSlots is null || exponentSlots.Length < slotCount)
{
exponentSlots?.Dispose();
exponentSlots = accelerator.Allocate1D<int>(slotCount);
kernels.SmallPrimeFactorExponentSlots = exponentSlots;
}
MemoryBuffer1D<int, Stride1D.Dense>? countSlot = kernels.SmallPrimeFactorCountSlot;
if (countSlot is null)
{
countSlot = accelerator.Allocate1D<int>(1);
kernels.SmallPrimeFactorCountSlot = countSlot;
}
MemoryBuffer1D<ulong, Stride1D.Dense>? remainingSlot = kernels.SmallPrimeFactorRemainingSlot;
if (remainingSlot is null)
{
remainingSlot = accelerator.Allocate1D<ulong>(1);
kernels.SmallPrimeFactorRemainingSlot = remainingSlot;
}
return new SmallPrimeFactorScratch(primeSlots!, exponentSlots!, countSlot!, remainingSlot!);
}
}
public static GpuKernelLease GetKernel(bool useGpuOrder)
{
var limiter = GpuPrimeWorkLimiter.Acquire();
var gpu = RentPreferred(preferCpu: !useGpuOrder);
var accelerator = gpu.Accelerator;
var kernels = GetKernels(accelerator);
return GpuKernelLease.Rent(limiter, gpu, kernels);
} }
/// <summary> /// <summary>
@@ -292,14 +219,14 @@ public class GpuKernelPool
/// <param name="action">Action to run with (Accelerator, Stream).</param> /// <param name="action">Action to run with (Accelerator, Stream).</param>
public static void Run(Action<Accelerator, AcceleratorStream> action) public static void Run(Action<Accelerator, AcceleratorStream> action)
{ {
var lease = GetKernel(useGpuOrder: true); // GpuPrimeWorkLimiter.Acquire();
var execution = lease.EnterExecutionScope(); var acceleratorIndex = AcceleratorPool.Shared.Rent();
var accelerator = lease.Accelerator; var accelerator = _accelerators[acceleratorIndex];
var stream = accelerator.CreateStream(); var stream = AcceleratorStreamPool.Rent(acceleratorIndex);
action(accelerator, stream); action(accelerator, stream);
stream.Synchronize();
stream.Dispose(); stream.Dispose();
execution.Dispose(); accelerator.Dispose();
lease.Dispose(); // GpuPrimeWorkLimiter.Release();
} }
} }
+5 -36
View File
@@ -3,27 +3,18 @@ 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 +22,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;
}
}
}
+47 -17
View File
@@ -261,13 +261,13 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
public override readonly int GetHashCode() => HashCode.Combine(High, Low); public override readonly int GetHashCode() => HashCode.Combine(High, Low);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(in ReadOnlyGpuUInt128 other) => AddInternal(other.High, other.Low); public void Add(in ReadOnlyGpuUInt128 other) => Add(other.High, other.Low);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(GpuUInt128 other) => AddInternal(other.High, other.Low); public void Add(GpuUInt128 other) => Add(other.High, other.Low);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AddInternal(ulong otherHigh, ulong otherLow) public void Add(ulong otherHigh, ulong otherLow)
{ {
ulong originalLow = Low; ulong originalLow = Low;
// We're reusing originalLow for carry to avoid an extra local. // We're reusing originalLow for carry to avoid an extra local.
@@ -316,7 +316,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
return; return;
} }
AddInternal(valueHigh, valueLow); Add(valueHigh, valueLow);
if (CompareTo(in modulus) >= 0) if (CompareTo(in modulus) >= 0)
{ {
Sub(in modulus); Sub(in modulus);
@@ -589,9 +589,6 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GpuUInt128 BinaryGcd(GpuUInt128 u, GpuUInt128 v) public static GpuUInt128 BinaryGcd(GpuUInt128 u, GpuUInt128 v)
{ {
// TODO: Replace this scalar binary GCD with the branchless reduction from
// GpuUInt128BinaryGcdBenchmarks so CPU fallbacks stay aligned with the GPU kernel
// performance when resolving large divisor residues.
if (u.IsZero) if (u.IsZero)
{ {
return v; return v;
@@ -602,26 +599,50 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
return u; return u;
} }
int shift = TrailingZeroCount(new GpuUInt128(u.High | v.High, u.Low | v.Low)); ulong combinedLow = u.Low | v.Low;
int shift;
if (combinedLow != 0UL)
{
shift = BitOperations.TrailingZeroCount(combinedLow);
}
else
{
ulong combinedHigh = u.High | v.High;
shift = 64 + BitOperations.TrailingZeroCount(combinedHigh);
}
int zu = TrailingZeroCount(u); int zu = TrailingZeroCount(u);
u >>= zu; u >>= zu;
do GpuUInt128 currentV = v;
while (true)
{ {
int zv = TrailingZeroCount(v); int zv = TrailingZeroCount(currentV);
v >>= zv; currentV >>= zv;
if (u > v)
bool swap = u > currentV;
GpuUInt128 min = ConditionalSelect(u, currentV, swap);
GpuUInt128 max = ConditionalSelect(currentV, u, swap);
max.Sub(min);
if (max.IsZero)
{ {
(u, v) = (v, u); return min << shift;
} }
v -= u; u = min;
currentV = max;
} }
while (!v.IsZero);
return u << shift;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static GpuUInt128 ConditionalSelect(in GpuUInt128 left, in GpuUInt128 right, bool useRight)
{
ulong mask = useRight ? ulong.MaxValue : 0UL;
return new GpuUInt128(
left.High ^ ((left.High ^ right.High) & mask),
left.Low ^ ((left.Low ^ right.Low) & mask));
}
public void Sub(ulong value) public void Sub(ulong value)
{ {
@@ -1529,6 +1550,15 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
high = hi; high = hi;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Pow2()
{
ulong operand = Low;
Low = operand * operand;
ulong highProduct = operand * High;
High = highProduct + MulHigh(operand, operand);
}
} }
// TODO: Check if the TODO below is still relevant. // TODO: Check if the TODO below is still relevant.
+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,58 @@
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
public sealed class KernelContainer
{
// Serializes first-time initialization of kernels/buffers per accelerator.
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>? Order;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>? Incremental;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>,
ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>,
ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2Mod;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? IncrementalOrder;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2ModOrder;
public Kernel? SmallPrimeFactor;
public Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? SpecialMax;
// Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length.
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallCycles;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastOne;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastSeven;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastOne;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastSeven;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimeFactorsPrimes;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorsSquares;
public void Dispose()
{
// Order = null;
// Incremental = null;
// Pow2Mod = null;
// IncrementalOrder = null;
// Pow2ModOrder = null;
// SmallPrimeFactor = null;
// SpecialMax = null;
// SmallCycles?.Dispose();
// SmallPrimesLastOne?.Dispose();
// SmallPrimesLastSeven?.Dispose();
// SmallPrimesPow2LastOne?.Dispose();
// SmallPrimesPow2LastSeven?.Dispose();
// SmallPrimeFactorsPrimes?.Dispose();
// SmallPrimeFactorsSquares?.Dispose();
}
}
+14
View File
@@ -0,0 +1,14 @@
namespace PerfectNumbers.Core.Gpu;
[Flags]
public enum KernelType
{
None,
SmallCycles,
SmallPrimes,
Pow2ModKernelScan,
OrderKernelScan,
IncrementalKernelScan,
IncrementalOrderKernelScan,
Pow2ModOrderKernelScan
}
@@ -1,7 +1,4 @@
using ILGPU; using ILGPU;
using ILGPU.Algorithms;
using ILGPU.Runtime;
using PerfectNumbers.Core;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -53,15 +50,28 @@ internal static class DivisorByDivisorKernels
int endIndex = offset + count; int endIndex = offset + count;
if (hasCycle) if (hasCycle)
{ {
GpuUInt128 remainderValue = new(cycleRemainder);
for (int exponentIndex = offset + 1; exponentIndex < endIndex; exponentIndex++) for (int exponentIndex = offset + 1; exponentIndex < endIndex; exponentIndex++)
{ {
ulong exponent = exponents[exponentIndex]; ulong exponent = exponents[exponentIndex];
ulong delta = exponent - previousExponent; ulong delta = exponent - previousExponent;
previousExponent = exponent; previousExponent = exponent;
remainderValue.AddMod(delta, cycleLength); if (delta >= cycleLength)
cycleRemainder = remainderValue.Low; {
delta -= cycleLength;
if (delta >= cycleLength)
{
delta %= cycleLength;
}
}
ulong nextRemainder = cycleRemainder + delta;
if (nextRemainder >= cycleLength)
{
nextRemainder -= cycleLength;
}
cycleRemainder = nextRemainder;
byte hit = 0; byte hit = 0;
if (cycleRemainder == 0UL) if (cycleRemainder == 0UL)
@@ -1,6 +1,5 @@
using ILGPU; using ILGPU;
using ILGPU.Algorithms; using ILGPU.Algorithms;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -1,5 +1,4 @@
using ILGPU; using ILGPU;
using ILGPU.Algorithms;
using ILGPU.Runtime; using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -1,7 +1,5 @@
using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Runtime;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -305,7 +303,7 @@ internal static class LucasLehmerKernels
states[idx] = s; states[idx] = s;
} }
public static void Kernel(Index1D index, ulong exponent, GpuUInt128 modulus, ArrayView<GpuUInt128> state) public static void Kernel(Index1D _, ulong exponent, GpuUInt128 modulus, ArrayView<GpuUInt128> state)
{ {
// Lucas–Lehmer iteration in the field GF(2^p-1). // Lucas–Lehmer iteration in the field GF(2^p-1).
// For p < 128, use a Mersenne-specific squaring + reduction to avoid // For p < 128, use a Mersenne-specific squaring + reduction to avoid
@@ -1,5 +1,6 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -32,7 +33,7 @@ internal static class NttBarrettKernels
} }
// Barrett-based squaring: v = v^2 mod n for 128-bit n // Barrett-based squaring: v = v^2 mod n for 128-bit n
public static void SquareBarrett128Kernel(Index1D index, ArrayView<GpuUInt128> data, ulong modHigh, ulong modLow, ulong muHigh, ulong muLow) public static void SquareBarrett128Kernel(Index1D index, AcceleratorStream stream, ArrayView<GpuUInt128> data, ulong modHigh, ulong modLow, ulong muHigh, ulong muLow)
{ {
var v = data[index]; var v = data[index];
Mul128Full(v.High, v.Low, v.High, v.Low, out var z3, out var z2, out var z1, out var z0); Mul128Full(v.High, v.Low, v.High, v.Low, out var z3, out var z2, out var z1, out var z0);
@@ -1,5 +1,6 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -43,7 +44,7 @@ internal static class NttMontgomeryKernels
data[index] = new GpuUInt128(0UL, v); data[index] = new GpuUInt128(0UL, v);
} }
public static void SquareMont64Kernel(Index1D index, ArrayView<GpuUInt128> data, ulong modulus, ulong nPrime) public static void SquareMont64Kernel(Index1D index, AcceleratorStream stream, ArrayView<GpuUInt128> data, ulong modulus, ulong nPrime)
{ {
ulong aR = data[index].Low; ulong aR = data[index].Low;
ulong v = MontMul64(aR, aR, modulus, nPrime); ulong v = MontMul64(aR, aR, modulus, nPrime);
@@ -1,5 +1,4 @@
using ILGPU; using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -1,5 +1,4 @@
using ILGPU; using ILGPU;
using ILGPU.Algorithms;
using ILGPU.Runtime; using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -1,18 +1,26 @@
using System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu.Kernels;
internal static class Pow2MontgomeryKernels internal static class Pow2MontgomeryKernels
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static GpuUInt128 AdvancePolynomialGpu(GpuUInt128 x, ulong c, ulong modulus)
{
x.Pow2();
x.AddMod(c, modulus);
return x;
}
public static void Pow2MontgomeryKernelKeepMontgomery( public static void Pow2MontgomeryKernelKeepMontgomery(
Index1D index, Index1D index,
ArrayView1D<ulong, Stride1D.Dense> exponents, ArrayView1D<ulong, Stride1D.Dense> exponents,
MontgomeryDivisorData divisor, MontgomeryDivisorData divisor,
ArrayView1D<ulong, Stride1D.Dense> results) ArrayView1D<ulong, Stride1D.Dense> results)
{ {
results[index] = ULongExtensions.Pow2MontgomeryModWindowedGpuKeepMontgomery(divisor, exponents[index]); results[0] = ULongExtensions.Pow2MontgomeryModWindowedGpuKeepMontgomery(divisor, exponents[0]);
} }
public static void Pow2MontgomeryKernelConvertToStandard( public static void Pow2MontgomeryKernelConvertToStandard(
@@ -21,6 +29,52 @@ internal static class Pow2MontgomeryKernels
MontgomeryDivisorData divisor, MontgomeryDivisorData divisor,
ArrayView1D<ulong, Stride1D.Dense> results) ArrayView1D<ulong, Stride1D.Dense> results)
{ {
results[index] = ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisor, exponents[index]); results[0] = ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisor, exponents[0]);
}
public static void TryPollardRhoKernel(ulong n, int limit, ArrayView1D<ulong, Stride1D.Dense> randomState, ArrayView1D<byte, Stride1D.Dense> factored, ArrayView1D<ulong, Stride1D.Dense> factor)
{
factored[0] = 0;
if ((n & 1UL) == 0UL)
{
factor[0] = 2UL;
factored[0] = 1;
return;
}
int attempt = 0;
GpuUInt128 nGpuUInt128 = (GpuUInt128)n;
DeterministicRandomGpu random = new(randomState[0]);
while (attempt < limit)
{
ulong c = (random.NextUInt64() % (n - 1UL)) + 1UL;
GpuUInt128 x = (random.NextUInt64() % (n - 2UL)) + 2UL;
GpuUInt128 y = x;
GpuUInt128 d = GpuUInt128.One;
while (d.CompareTo(GpuUInt128.One) == 0)
{
x = AdvancePolynomialGpu(x, c, modulus: n);
y = AdvancePolynomialGpu(y, c, n);
y = AdvancePolynomialGpu(y, c, n);
GpuUInt128 diff = x.CompareTo(y) > 0 ? x - y : y - x;
d = GpuUInt128.BinaryGcd(diff, nGpuUInt128);
}
if (d != n)
{
if (d.High == 0)
{
factor[0] = d.Low;
factored[0] = 1;
return;
}
}
attempt++;
}
randomState[0] = random.State;
} }
} }
@@ -1,15 +1,51 @@
using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Algorithms.Random;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
internal static partial class PrimeOrderGpuHeuristics internal static partial class PrimeOrderGpuHeuristics
{ {
internal static void CheckFactorsKernel(
int count,
ulong phi,
ArrayView1D<KeyValuePair<ulong, int>, Stride1D.Dense> toTest,
MontgomeryDivisorData divisorData,
ArrayView1D<ulong, Stride1D.Dense> result
)
{
ulong order = phi;
int entryCount = count;
for (int i = 0; i < entryCount; i++)
{
(ulong primeFactor, int exponent) = toTest[i];
for (int iteration = 0; iteration < exponent; iteration++)
{
ulong candidate = order / primeFactor;
ulong remainder = order - (candidate * primeFactor);
// ulong remainder = order % primeFactor;
if (remainder != 0UL)
{
break;
}
// ulong candidate = order / primeFactor;
if (ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisorData, candidate) == 1UL)
{
order = candidate;
continue;
}
break;
}
}
result[0] = order;
}
/// This kernel always sets the result and operates within the allowed / required bounds, never accessing elements outside of them. /// This kernel always sets the result and operates within the allowed / required bounds, never accessing elements outside of them.
/// It sets the value only when it needs to. The callers don't need to clear the output / input buffers. /// It sets the value only when it needs to. The callers don't need to clear the output / input buffers.
internal static void PartialFactorKernel( internal static void PartialFactorKernel(
@@ -1170,16 +1206,6 @@ internal static partial class PrimeOrderGpuHeuristics
remainders[index] = ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisor, exponent); remainders[index] = ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisor, exponent);
} }
private static Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>> GetPow2ModWideKernel(Accelerator accelerator)
{
return Pow2ModKernelWideCache.GetOrAdd(accelerator, static accel =>
{
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>>(Pow2ModKernelWide);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>>>();
});
}
/// This kernel always sets the result of the corresponding element. Callers don't need to clear the output buffers. /// This kernel always sets the result of the corresponding element. Callers don't need to clear the output buffers.
internal static void Pow2ModKernelWide(Index1D index, ArrayView1D<GpuUInt128, Stride1D.Dense> exponents, GpuUInt128 modulus, ArrayView1D<GpuUInt128, Stride1D.Dense> remainders) internal static void Pow2ModKernelWide(Index1D index, ArrayView1D<GpuUInt128, Stride1D.Dense> exponents, GpuUInt128 modulus, ArrayView1D<GpuUInt128, Stride1D.Dense> remainders)
{ {
@@ -1367,5 +1393,4 @@ internal static partial class PrimeOrderGpuHeuristics
ulong directMask = (1UL << windowBitCount) - 1UL; ulong directMask = (1UL << windowBitCount) - 1UL;
return exponent.Low & directMask; return exponent.Low & directMask;
} }
} }
@@ -0,0 +1,79 @@
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
internal static partial class PrimeOrderGpuHeuristics
{
internal static void EvaluateSpecialMaxCandidatesKernel(
Index1D index,
ulong phi,
ArrayView1D<ulong, Stride1D.Dense> factors,
int factorCount,
MontgomeryDivisorData divisor,
ArrayView1D<ulong, Stride1D.Dense> candidates,
ArrayView1D<ulong, Stride1D.Dense> resultOut)
{
if (index != 0)
{
return;
}
int actual = 0;
for (int i = 0; i < factorCount; i++)
{
ulong factor = factors[i];
if (factor <= 1UL)
{
continue;
}
ulong reduced = phi / factor;
if (reduced == 0UL)
{
continue;
}
candidates[actual] = reduced;
actual++;
}
if (actual == 0)
{
resultOut[0] = 1;
return;
}
for (int i = 1; i < actual; i++)
{
ulong current = candidates[i];
int insert = i - 1;
while (insert >= 0 && candidates[insert] > current)
{
candidates[insert + 1] = candidates[insert];
insert--;
}
candidates[insert + 1] = current;
}
var divisorPartial = new GpuDivisorPartialData(divisor.Modulus);
var stepper = new ExponentRemainderStepperGpu(divisorPartial);
if (stepper.InitializeIsUnityGpu(candidates[0]))
{
Atomic.Exchange(ref resultOut[0], 0);
return;
}
for (int i = 1; i < actual; i++)
{
if (stepper.ComputeNextIsUnityGpu(candidates[i]))
{
Atomic.Exchange(ref resultOut[0], 0);
return;
}
}
Atomic.Exchange(ref resultOut[0], 1);
}
}
@@ -1,33 +1,73 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Algorithms; using ILGPU.Algorithms;
using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
public enum HeuristicGpuDivisorTableKind : byte
{
GroupA = 0,
GroupBEnding1 = 1,
GroupBEnding7 = 7,
GroupBEnding9 = 9,
}
internal static class PrimeTesterKernels internal static class PrimeTesterKernels
{ {
// GPU kernel: small-prime sieve only. Returns 1 if passes sieve (probable prime), 0 otherwise. // GPU kernel: small-prime sieve only. Returns 1 if passes sieve (probable prime), 0 otherwise.
public static void SmallPrimeSieveKernel(Index1D index, ArrayView<ulong> numbers, ArrayView<uint> smallPrimes, ArrayView<byte> results) public static void SmallPrimeSieveKernel(
Index1D index,
ArrayView<ulong> numbers,
ArrayView<uint> smallPrimesLastOne,
ArrayView<uint> smallPrimesLastSeven,
ArrayView<uint> smallPrimesLastThree,
ArrayView<uint> smallPrimesLastNine,
ArrayView<ulong> smallPrimesPow2LastOne,
ArrayView<ulong> smallPrimesPow2LastSeven,
ArrayView<ulong> smallPrimesPow2LastThree,
ArrayView<ulong> smallPrimesPow2LastNine,
ArrayView<byte> results)
{ {
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;
// - n >= 31 (the scanner starts at 136,279,841 and test mode seeds 31) ArrayView<ulong> primeSquares;
// - 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;
default:
return;
}
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,69 @@ 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 HeuristicTrialCombinedDivisionKernel(
Index1D index,
byte nMod10,
ArrayView<int> resultFlag,
ulong n,
ulong maxDivisorSquare,
HeuristicCombinedGpuViews tables)
{ {
ulong divisor = divisors[index]; // byte nMod10 = (byte)(n % 10UL);
if (divisor <= 1UL) ArrayView1D<ulong, Stride1D.Dense> divisors = tables.SelectDivisors(nMod10);
ArrayView1D<ulong, Stride1D.Dense> divisorSquares = tables.SelectDivisorSquares(nMod10);
int divisorLength = (int)divisorSquares.Length;
int threadIndex = index;
if (threadIndex >= divisorLength)
{ {
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 HeuristicTrialGroupABDivisionKernel(
Index1D index,
ArrayView<int> resultFlag,
ulong n,
ulong maxDivisorSquare,
HeuristicGpuDivisorTableKind tableKind,
HeuristicGroupABGpuViews tables)
{
ArrayView1D<ulong, Stride1D.Dense> divisors = tables.SelectDivisors(tableKind);
ArrayView1D<ulong, Stride1D.Dense> divisorSquares = tables.SelectDivisorSquares(tableKind);
int divisorLength = (int)divisorSquares.Length;
int threadIndex = index;
if (threadIndex >= divisorLength)
{
return;
}
ulong divisorSquare = divisorSquares[threadIndex];
if (divisorSquare > maxDivisorSquare)
{
return;
}
ulong divisor = divisors[threadIndex];
divisor = n % divisor;
if (divisor == 0UL)
{
resultFlag[0] = 1;
}
} }
public static void SharesFactorKernel(Index1D index, ArrayView<ulong> numbers, ArrayView<byte> results) public static void SharesFactorKernel(Index1D index, ArrayView<ulong> numbers, ArrayView<byte> results)
@@ -60,35 +153,37 @@ internal static class PrimeTesterKernels
private static ulong BinaryGcdGpu(ulong u, ulong v) private static ulong BinaryGcdGpu(ulong u, ulong v)
{ {
// TODO: Replace this inline GPU binary GCD with the kernel extracted from
// GpuUInt128BinaryGcdBenchmarks via GpuKernelPool so device callers reuse the
// fully unrolled ladder instead of this branchy fallback.
bool uIsZero = u == 0UL; bool uIsZero = u == 0UL;
bool vIsZero = v == 0UL; bool vIsZero = v == 0UL;
bool eitherZero = uIsZero | vIsZero; bool eitherZero = uIsZero | vIsZero;
ulong zeroResult = uIsZero ? v : u; ulong zeroResult = uIsZero ? v : u;
ulong combined = u | v | 1UL; ulong combined = (u | v) | 1UL;
int shift = XMath.TrailingZeroCount(combined); int shift = XMath.TrailingZeroCount(combined);
ulong currentU = u >> XMath.TrailingZeroCount(u | 1UL);
ulong currentV = v >> XMath.TrailingZeroCount(v | 1UL);
ulong normalizedU = u >> XMath.TrailingZeroCount(u | 1UL); while (true)
ulong normalizedV = v >> XMath.TrailingZeroCount(v | 1UL);
ulong currentU = normalizedU;
ulong currentV = normalizedV;
bool loopCondition = currentV != 0UL;
while (loopCondition)
{ {
currentV >>= XMath.TrailingZeroCount(currentV); currentV >>= XMath.TrailingZeroCount(currentV);
ulong minValue = XMath.Min(currentU, currentV); bool swap = currentU > currentV;
ulong maxValue = XMath.Max(currentU, currentV); ulong minValue = ConditionalSelect(currentU, currentV, swap);
currentU = minValue; ulong maxValue = ConditionalSelect(currentV, currentU, swap);
currentV = maxValue - minValue; currentV = maxValue - minValue;
loopCondition = currentV != 0UL; currentU = minValue;
} if (currentV == 0UL)
{
ulong gcd = currentU << shift; ulong gcd = currentU << shift;
return eitherZero ? zeroResult : gcd; return eitherZero ? zeroResult : gcd;
} }
} }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong ConditionalSelect(ulong left, ulong right, bool useRight)
{
ulong mask = useRight ? ulong.MaxValue : 0UL;
return left ^ ((left ^ right) & mask);
}
}
@@ -1,12 +1,10 @@
using System;
using System.Buffers; using System.Buffers;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using PerfectNumbers.Core;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -23,15 +21,26 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
private ulong _divisorLimit; private ulong _divisorLimit;
// private bool _isConfigured; // private bool _isConfigured;
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>> _kernelCache = new(AcceleratorReferenceComparer.Instance); [ThreadStatic]
private readonly ConcurrentBag<BatchResources> _resourcePool = new(); private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>>? _kernel;
private readonly ConcurrentBag<GpuContextPool.GpuContextLease> _acceleratorPool = new();
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
private Action<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> GetKernel(Accelerator accelerator) => private readonly ConcurrentBag<BatchResources> _resourcePool = [];
_kernelCache.GetOrAdd(accelerator, acc => private readonly ConcurrentBag<DivisorScanSession> _sessionPool = [];
acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>(DivisorByDivisorKernels.CheckKernel));
private static Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> GetKernel(Accelerator accelerator)
{
var pool = _kernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>(DivisorByDivisorKernels.CheckKernel);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>>();
pool[accelerator] = cached;
return cached;
}
public int GpuBatchSize public int GpuBatchSize
{ {
@@ -59,7 +68,9 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
// _isConfigured = true; // _isConfigured = true;
} }
public bool IsPrime(ulong prime, out bool divisorsExhausted) private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor)
{ {
ulong allowedMax; ulong allowedMax;
int batchCapacity; int batchCapacity;
@@ -91,18 +102,25 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ulong processedCount; ulong processedCount;
ulong lastProcessed; ulong lastProcessed;
var gpuLease = RentAccelerator(); // GpuPrimeWorkLimiter();
var accelerator = gpuLease.Accelerator; // int acceleratorIndex = AcceleratorPool.Shared.Rent();
// var accelerator = _accelerators[acceleratorIndex];
var gpu = PrimeOrderCalculatorAccelerator.Rent(batchCapacity);
var acceleratorIndex = gpu.AcceleratorIndex;
var accelerator = gpu.Accelerator;
var stream = AcceleratorStreamPool.Rent(acceleratorIndex);
Monitor.Enter(gpuLease.ExecutionLock); // Monitor.Enter(gpuLease.ExecutionLock);
var kernel = GetKernel(accelerator); var kernel = GetKernel(accelerator);
var resources = RentBatchResources(accelerator, batchCapacity); var resources = RentBatchResources(accelerator, batchCapacity);
composite = CheckDivisors( composite = CheckDivisors(
gpu,
prime, prime,
allowedMax, allowedMax,
kernel, kernel,
stream,
resources.DivisorDataBuffer, resources.DivisorDataBuffer,
resources.OffsetBuffer, resources.OffsetBuffer,
resources.CountBuffer, resources.CountBuffer,
@@ -112,7 +130,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,
@@ -121,17 +138,21 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
out coveredRange, out coveredRange,
out processedCount); out processedCount);
AcceleratorStreamPool.Return(acceleratorIndex, stream);
ReturnBatchResources(resources); ReturnBatchResources(resources);
Monitor.Exit(gpuLease.ExecutionLock); PrimeOrderCalculatorAccelerator.Return(gpu);
ReturnAccelerator(gpuLease); // Monitor.Exit(gpuLease.ExecutionLock);
// GpuPrimeWorkLimiter.Release();
if (composite) if (composite)
{ {
divisorsExhausted = true; divisorsExhausted = true;
divisor = lastProcessed;
return false; return false;
} }
divisorsExhausted = coveredRange; divisorsExhausted = coveredRange;
divisor = 0UL;
return true; return true;
} }
@@ -165,9 +186,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
private static bool CheckDivisors( private static bool CheckDivisors(
PrimeOrderCalculatorAccelerator gpu,
ulong prime, ulong prime,
ulong allowedMax, ulong allowedMax,
Action<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> kernel, Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> kernel,
AcceleratorStream stream,
MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense> divisorDataBuffer, MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense> divisorDataBuffer,
MemoryBuffer1D<int, Stride1D.Dense> offsetBuffer, MemoryBuffer1D<int, Stride1D.Dense> offsetBuffer,
MemoryBuffer1D<int, Stride1D.Dense> countBuffer, MemoryBuffer1D<int, Stride1D.Dense> countBuffer,
@@ -177,7 +200,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,
@@ -188,7 +210,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
{ {
int batchCapacity = (int)divisorDataBuffer.Length; int batchCapacity = (int)divisorDataBuffer.Length;
bool composite = false; bool composite = false;
bool processedAll = false; bool processedAll;
processedCount = 0UL; processedCount = 0UL;
lastProcessed = 0UL; lastProcessed = 0UL;
@@ -219,24 +241,23 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128; ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128;
ulong remainingCount = maxK; ulong remainingCount = maxK;
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); 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();
@@ -271,37 +292,50 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
chunkCount = (int)remainingCount; chunkCount = (int)remainingCount;
} }
int filteredCount = 0;
UInt128 nextDivisor128 = currentDivisor128; UInt128 nextDivisor128 = currentDivisor128;
int admissibleCount = 0;
byte localRemainder10 = remainder10; byte localRemainder10 = remainder10;
byte localRemainder8 = remainder8; byte localRemainder8 = remainder8;
byte localRemainder5 = remainder5;
byte localRemainder3 = remainder3; byte localRemainder3 = remainder3;
byte localRemainder7 = remainder7; byte localRemainder7 = remainder7;
byte localRemainder11 = remainder11; byte localRemainder11 = remainder11;
for (int i = 0; i < chunkCount; i++) for (int i = 0; i < chunkCount; i++)
{ {
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, decimalMask))
{ {
filteredStorage[filteredCount++] = candidate; ulong divisorValue = (ulong)nextDivisor128;
MontgomeryDivisorData montgomeryData = MontgomeryDivisorData.FromModulus(divisorValue);
ulong divisorCycle = ResolveDivisorCycle(gpu, divisorValue, prime, in montgomeryData);
if (divisorCycle == prime)
{
processedCount += (ulong)(i + 1);
lastProcessed = divisorValue;
coveredRange = true;
return true;
}
divisorSpan[admissibleCount] = divisorValue;
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 = AddMod8(localRemainder8, step8); 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;
@@ -313,50 +347,25 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
currentDivisor128 = nextDivisor128; currentDivisor128 = nextDivisor128;
remainingCount -= (ulong)chunkCount; remainingCount -= (ulong)chunkCount;
if (filteredCount == 0)
{
continue;
}
int admissibleCount = 0;
for (int i = 0; i < filteredCount; i++)
{
ulong divisorValue = filteredStorage[i];
MontgomeryDivisorData montgomeryData = MontgomeryDivisorData.FromModulus(divisorValue);
ulong divisorCycle = ResolveDivisorCycle(divisorValue, prime, in montgomeryData);
if (divisorCycle != prime)
{
continue;
}
divisorSpan[admissibleCount] = divisorValue;
exponentSpan[admissibleCount] = prime;
divisorDataSpan[admissibleCount] = new GpuDivisorPartialData(divisorValue);
offsetSpan[admissibleCount] = admissibleCount;
countSpan[admissibleCount] = 1;
cycleSpan[admissibleCount] = divisorCycle;
admissibleCount++;
}
if (admissibleCount == 0) if (admissibleCount == 0)
{ {
continue; continue;
} }
divisorDataView.CopyFromCPU(ref divisorDataRef, admissibleCount); divisorDataView.CopyFromCPU(stream, ref divisorDataRef, admissibleCount);
offsetView.CopyFromCPU(ref offsetRef, admissibleCount); offsetView.CopyFromCPU(stream, ref offsetRef, admissibleCount);
countView.CopyFromCPU(ref countRef, admissibleCount); countView.CopyFromCPU(stream, ref countRef, admissibleCount);
cycleView.CopyFromCPU(ref cycleRef, admissibleCount); cycleView.CopyFromCPU(stream, ref cycleRef, admissibleCount);
exponentViewDevice.CopyFromCPU(ref exponentRef, admissibleCount); exponentViewDevice.CopyFromCPU(stream, ref exponentRef, admissibleCount);
int sentinel = int.MaxValue; int sentinel = int.MaxValue;
hitIndexView.CopyFromCPU(ref sentinel, 1); hitIndexView.CopyFromCPU(stream, ref sentinel, 1);
kernel(admissibleCount, divisorDataView, offsetView, countView, exponentViewDevice, cycleView, hitsView, hitIndexView); kernel(stream, new Index1D(admissibleCount), divisorDataView, offsetView, countView, exponentViewDevice, cycleView, hitsView, hitIndexView);
int firstHit = sentinel; int firstHit = sentinel;
hitIndexView.CopyToCPU(ref firstHit, 1); hitIndexView.CopyToCPU(stream, ref firstHit, 1);
stream.Synchronize();
int hitIndex = firstHit >= admissibleCount ? -1 : firstHit; int hitIndex = firstHit >= admissibleCount ? -1 : firstHit;
bool hitFound = hitIndex >= 0; bool hitFound = hitIndex >= 0;
composite = hitFound; composite = hitFound;
@@ -371,9 +380,9 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong ResolveDivisorCycle(ulong divisor, ulong prime, in MontgomeryDivisorData divisorData) private static ulong ResolveDivisorCycle(PrimeOrderCalculatorAccelerator gpu, ulong divisor, ulong prime, in MontgomeryDivisorData divisorData)
{ {
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(divisor, prime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL) if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(gpu, divisor, prime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL)
{ {
return MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); return MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
} }
@@ -382,19 +391,62 @@ 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;
int sum = value + delta;
if (result >= modulus) if (sum >= Modulus)
{ {
result -= modulus; sum -= 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod8(byte value, byte delta) private static byte AddMod8(byte value, byte delta)
{ {
@@ -402,9 +454,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
private static ulong ComputeDivisorLimitFromMaxPrimeGpu(ulong maxPrime) private static ulong ComputeDivisorLimitFromMaxPrimeGpu(ulong maxPrime)
{ {
// The --mersenne=bydivisor flow in EvenPerfectBitScanner only calls ConfigureFromMaxPrime with primes greater than 1, // The --mersenne=bydivisor flow in EvenPerfectBitScanner only calls ConfigureFromMaxPrime with primes greater than 1,
@@ -428,21 +477,34 @@ 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
{ {
private readonly MersenneNumberDivisorByDivisorGpuTester _owner; private readonly MersenneNumberDivisorByDivisorGpuTester _owner;
private readonly GpuContextPool.GpuContextLease _lease; private readonly PrimeOrderCalculatorAccelerator _primeTesterAccelerator;
private readonly Accelerator _accelerator; private readonly Accelerator _accelerator;
private Action<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> _kernel = null!; private readonly AcceleratorStream _stream;
private Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>> _kernel = null!;
private MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense> _divisorBuffer = null!; private MemoryBuffer1D<GpuDivisorPartialData, Stride1D.Dense> _divisorBuffer = null!;
private MemoryBuffer1D<int, Stride1D.Dense> _offsetBuffer = null!; private MemoryBuffer1D<int, Stride1D.Dense> _offsetBuffer = null!;
private MemoryBuffer1D<int, Stride1D.Dense> _countBuffer = null!; private MemoryBuffer1D<int, Stride1D.Dense> _countBuffer = null!;
@@ -452,35 +514,42 @@ 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); // GpuPrimeWorkLimiter.Acquire();
_accelerator = _lease.Accelerator; _primeTesterAccelerator = PrimeOrderCalculatorAccelerator.Rent(1);
Accelerator accelerator = _primeTesterAccelerator.Accelerator;
_accelerator = accelerator;
_stream = accelerator.CreateStream();
_capacity = Math.Max(1, owner._gpuBatchSize); _capacity = Math.Max(1, owner._gpuBatchSize);
} }
internal void Reset() internal void Reset()
{ {
_disposed = false;
} }
private void EnsureExecutionResourcesLocked(int requiredCapacity) private void EnsureExecutionResourcesLocked(int requiredCapacity)
{ {
Accelerator accelerator = _accelerator;
if (_kernel == null) if (_kernel == null)
{ {
_kernel = _owner.GetKernel(_accelerator); _kernel = GetKernel(_accelerator);
} }
if (_divisorBuffer == null) if (_divisorBuffer == null)
{ {
_divisorBuffer = _accelerator.Allocate1D<GpuDivisorPartialData>(1); lock(accelerator)
_offsetBuffer = _accelerator.Allocate1D<int>(1); {
_countBuffer = _accelerator.Allocate1D<int>(1); _divisorBuffer = accelerator.Allocate1D<GpuDivisorPartialData>(1);
_cycleBuffer = _accelerator.Allocate1D<ulong>(1); _offsetBuffer = accelerator.Allocate1D<int>(1);
_firstHitBuffer = _accelerator.Allocate1D<int>(1); _countBuffer = accelerator.Allocate1D<int>(1);
_cycleBuffer = accelerator.Allocate1D<ulong>(1);
_firstHitBuffer = accelerator.Allocate1D<int>(1);
}
} }
bool allocateBuffers = _exponentsBuffer == null; bool allocateBuffers = _exponentsBuffer == null;
@@ -503,19 +572,18 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
{ {
int desiredCapacity = requiredCapacity > _capacity ? requiredCapacity : _capacity; int desiredCapacity = requiredCapacity > _capacity ? requiredCapacity : _capacity;
_capacity = desiredCapacity; _capacity = desiredCapacity;
_exponentsBuffer = _accelerator.Allocate1D<ulong>(_capacity); lock (accelerator)
_hitBuffer = _accelerator.Allocate1D<byte>(_capacity); {
_hostBuffer = ThreadStaticPools.UlongPool.Rent(_capacity); _exponentsBuffer = accelerator.Allocate1D<ulong>(desiredCapacity);
_hitBuffer = accelerator.Allocate1D<byte>(desiredCapacity);
}
_hostBuffer = ThreadStaticPools.UlongPool.Rent(desiredCapacity);
} }
} }
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits) public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits)
{ {
if (_disposed)
{
throw new ObjectDisposedException(nameof(DivisorScanSession));
}
int length = primes.Length; int length = primes.Length;
// EvenPerfectBitScanner always supplies at least one exponent per divisor check, so the guard stays commented out. // EvenPerfectBitScanner always supplies at least one exponent per divisor check, so the guard stays commented out.
// if (length == 0) // if (length == 0)
@@ -536,7 +604,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
if (cycle == 0UL) if (cycle == 0UL)
{ {
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(divisor, firstPrime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL) var gpu = _primeTesterAccelerator;
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(gpu, divisor, firstPrime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL)
{ {
cycle = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed); cycle = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
} }
@@ -551,7 +620,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
} }
Monitor.Enter(_lease.ExecutionLock); // Monitor.Enter(_lease.ExecutionLock);
EnsureExecutionResourcesLocked(length); EnsureExecutionResourcesLocked(length);
@@ -563,51 +632,42 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
ArrayView1D<ulong, Stride1D.Dense> exponentView = _exponentsBuffer.View; ArrayView1D<ulong, Stride1D.Dense> exponentView = _exponentsBuffer.View;
ArrayView1D<byte, Stride1D.Dense> hitView = _hitBuffer.View; ArrayView1D<byte, Stride1D.Dense> hitView = _hitBuffer.View;
AcceleratorStream stream = _stream;
GpuDivisorPartialData partialData = new GpuDivisorPartialData(divisor); GpuDivisorPartialData partialData = new GpuDivisorPartialData(divisor);
divisorView.CopyFromCPU(ref partialData, 1); divisorView.CopyFromCPU(stream, ref partialData, 1);
int offsetValue = 0; int offsetValue = 0;
offsetView.CopyFromCPU(ref offsetValue, 1); offsetView.CopyFromCPU(stream, ref offsetValue, 1);
int countValue = length; int countValue = length;
countView.CopyFromCPU(ref countValue, 1); countView.CopyFromCPU(stream, ref countValue, 1);
ulong cycleValue = cycle; ulong cycleValue = cycle;
cycleView.CopyFromCPU(ref cycleValue, 1); cycleView.CopyFromCPU(stream, ref cycleValue, 1);
Span<ulong> hostSpan = _hostBuffer.AsSpan(0, length); Span<ulong> hostSpan = _hostBuffer.AsSpan(0, length);
primes.CopyTo(hostSpan); primes.CopyTo(hostSpan);
ref ulong hostRef = ref MemoryMarshal.GetReference(hostSpan); ref ulong hostRef = ref MemoryMarshal.GetReference(hostSpan);
exponentView.CopyFromCPU(ref hostRef, length); exponentView.CopyFromCPU(stream, ref hostRef, length);
int sentinel = int.MaxValue; int sentinel = int.MaxValue;
firstHitView.CopyFromCPU(ref sentinel, 1); firstHitView.CopyFromCPU(stream, 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, exponentView, cycleView, hitView, firstHitView); kernel(stream, new Index1D(1), divisorView, offsetView, countView, exponentView, cycleView, hitView, firstHitView);
Span<byte> hitSlice = hits.Slice(0, length); Span<byte> hitSlice = hits.Slice(0, length);
ref byte hitRef = ref MemoryMarshal.GetReference(hitSlice); ref byte hitRef = ref MemoryMarshal.GetReference(hitSlice);
hitView.CopyToCPU(ref hitRef, length); hitView.CopyToCPU(stream, ref hitRef, length);
stream.Synchronize();
Monitor.Exit(_lease.ExecutionLock); // Monitor.Exit(_lease.ExecutionLock);
} }
public void Dispose() public void Return()
{ {
if (_disposed)
{
return;
}
_disposed = true;
_owner._sessionPool.Add(this); _owner._sessionPool.Add(this);
} }
} }
@@ -632,17 +692,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
} }
private GpuContextPool.GpuContextLease RentAccelerator()
{
if (_acceleratorPool.TryTake(out var lease))
{
return lease;
}
return GpuContextPool.RentPreferred(preferCpu: false);
}
private void ReturnAccelerator(GpuContextPool.GpuContextLease lease) => _acceleratorPool.Add(lease);
private BatchResources RentBatchResources(Accelerator accelerator, int capacity) private BatchResources RentBatchResources(Accelerator accelerator, int capacity)
{ {
@@ -657,7 +707,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;
@@ -694,8 +744,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!;
@@ -751,6 +799,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
} }
private void AllocateDeviceBuffers(Accelerator accelerator, int capacity) private void AllocateDeviceBuffers(Accelerator accelerator, int capacity)
{
lock (accelerator)
{ {
_divisorDataBuffer = accelerator.Allocate1D<GpuDivisorPartialData>(capacity); _divisorDataBuffer = accelerator.Allocate1D<GpuDivisorPartialData>(capacity);
_offsetBuffer = accelerator.Allocate1D<int>(capacity); _offsetBuffer = accelerator.Allocate1D<int>(capacity);
@@ -759,6 +809,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
_exponentBuffer = accelerator.Allocate1D<ulong>(capacity); _exponentBuffer = accelerator.Allocate1D<ulong>(capacity);
_hitsBuffer = accelerator.Allocate1D<byte>(capacity); _hitsBuffer = accelerator.Allocate1D<byte>(capacity);
_hitIndexBuffer = accelerator.Allocate1D<int>(1); _hitIndexBuffer = accelerator.Allocate1D<int>(1);
}
_deviceCapacity = capacity; _deviceCapacity = capacity;
} }
@@ -770,7 +822,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);
@@ -809,14 +860,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>();
@@ -887,7 +936,3 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession(); IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession();
} }
@@ -1,4 +1,3 @@
using System.Collections.Concurrent;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
@@ -9,11 +8,29 @@ namespace PerfectNumbers.Core.Gpu;
/// </summary> /// </summary>
public sealed class MersenneNumberDivisorGpuTester public sealed class MersenneNumberDivisorGpuTester
{ {
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>> _kernelCache = new(); [ThreadStatic]
private readonly ConcurrentDictionary<Accelerator, MemoryBuffer1D<byte, Stride1D.Dense>> _resultBuffers = new(); private static Dictionary<Accelerator, Queue<MemoryBuffer1D<byte, Stride1D.Dense>>>? _resultBuffers;
private Action<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>> GetKernel(Accelerator accelerator) => private static Queue<MemoryBuffer1D<byte, Stride1D.Dense>> GetQueue(Accelerator accelerator)
_kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>(DivisorKernels.Kernel)); {
var pool = _resultBuffers ??= [];
if (!pool.TryGetValue(accelerator, out var bufferQueue))
{
bufferQueue = [];
pool[accelerator] = bufferQueue;
}
return bufferQueue;
}
private static Action<AcceleratorStream, Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>> GetDivisorKernel(Accelerator accelerator)
{
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>(DivisorKernels.Kernel);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>>();
}
public static void BuildDivisorCandidates() public static void BuildDivisorCandidates()
{ {
@@ -35,27 +52,39 @@ public sealed class MersenneNumberDivisorGpuTester
_divisorCandidates = count == 0 ? [] : list[..count]; _divisorCandidates = count == 0 ? [] : list[..count];
} }
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
public bool IsDivisible(ulong exponent, in ReadOnlyGpuUInt128 divisor) public bool IsDivisible(ulong exponent, in ReadOnlyGpuUInt128 divisor)
{ {
var gpu = GpuContextPool.RentPreferred(preferCpu: false); // var accelerator = SharedGpuContext.CreateAccelerator();
var accelerator = gpu.Accelerator; var acceleratorIndex = AcceleratorPool.Shared.Rent();
var kernel = GetKernel(accelerator); var accelerator = _accelerators[acceleratorIndex];
var resultBuffer = _resultBuffers.GetOrAdd(accelerator, acc => acc.Allocate1D<byte>(1)); var stream = accelerator.CreateStream();
Queue<MemoryBuffer1D<byte, Stride1D.Dense>> resultBuffers = GetQueue(accelerator);
if (!resultBuffers.TryDequeue(out var resultBuffer))
{
// lock(accelerator)
{
resultBuffer = accelerator.Allocate1D<byte>(1);
}
}
// There is no point in clearing this buffer. We always override item [0] and never use it beyond item [0] // There is no point in clearing this buffer. We always override item [0] and never use it beyond item [0]
// resultBuffer.MemSetToZero(); // resultBuffer.MemSetToZero(stream);
kernel(1, exponent, divisor, resultBuffer.View); var divisorKernel = GetDivisorKernel(accelerator);
accelerator.Synchronize(); divisorKernel(stream, 1, exponent, divisor, resultBuffer.View);
Span<byte> result = stackalloc byte[1]; byte result = 0;
resultBuffer.View.CopyToCPU(ref result[0], 1); resultBuffer.View.CopyToCPU(stream, ref result, 1);
bool divisible = result[0] != 0; stream.Synchronize();
result[0] = 0; bool divisible = result != 0;
resultBuffer.View.CopyFromCPU(ref result[0], 1); resultBuffers.Enqueue(resultBuffer);
accelerator.Synchronize(); stream.Dispose();
gpu.Dispose(); // AcceleratorPool.Shared.Return(accelerator);
// accelerator.Dispose();
return divisible; return divisible;
} }
private static (ulong divisor, uint cycle)[]? _divisorCandidates = Array.Empty<(ulong divisor, uint cycle)>(); private static (ulong divisor, uint cycle)[]? _divisorCandidates = [];
public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted) public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted)
{ {
@@ -12,43 +12,45 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
private readonly GpuKernelType _kernelType = kernelType; private readonly GpuKernelType _kernelType = kernelType;
private readonly bool _useGpuOrder = useGpuOrder; private readonly bool _useGpuOrder = useGpuOrder;
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
{ {
throw new NotImplementedException($"GPU incremental scanning requires the device cycle heuristics implementation (kernel: {_kernelType}, GPU order: {_useGpuOrder})."); throw new NotImplementedException($"GPU incremental scanning requires the device cycle heuristics implementation (kernel: {_kernelType}, GPU order: {_useGpuOrder}).");
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); // GpuPrimeWorkLimiter.Acquire();
var execution = gpuLease.EnterExecutionScope(); var acceleratorIndex = AcceleratorPool.Shared.Rent();
var accelerator = gpuLease.Accelerator; var accelerator = _accelerators[acceleratorIndex];
var stream = gpuLease.Stream; var stream = accelerator.CreateStream();
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy
UInt128 kStart = 1UL; UInt128 kStart = 1UL;
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL; ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters
var pow2Kernel = gpuLease.Pow2ModKernel; KernelContainer kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.Pow2ModKernelScan | KernelType.IncrementalKernelScan);
var incKernel = gpuLease.IncrementalKernel; var pow2Kernel = kernels.Pow2Mod!;
var incKernel = kernels.Incremental!;
ulong step10 = (exponent.Mod10() << 1).Mod10(); ulong step10 = (exponent.Mod10() << 1).Mod10();
ulong step8 = ((exponent & 7UL) << 1) & 7UL; ulong step8 = ((exponent & 7UL) << 1) & 7UL;
ulong step3 = ((exponent % 3UL) << 1) % 3UL; ulong step3 = ((exponent % 3UL) << 1) % 3UL;
ulong step5 = ((exponent % 5UL) << 1) % 5UL; ulong step5 = ((exponent % 5UL) << 1) % 5UL;
GpuUInt128 twoPGpu = (GpuUInt128)twoP; GpuUInt128 twoPGpu = (GpuUInt128)twoP;
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); var smallCyclesView = GpuKernelPool.GetSmallCyclesOnDevice(kernels);
ResiduePrimeViews primeViews = default; ResiduePrimeViews primeViews = default;
if (_kernelType == GpuKernelType.Pow2Mod) if (_kernelType == GpuKernelType.Pow2Mod)
{ {
primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(accelerator); primeViews = GpuKernelPool.GetSmallPrimesOnDevice(kernels);
} }
var orderBuffer = accelerator.Allocate1D<ulong>(batchSize); var orderBuffer = accelerator.Allocate1D<ulong>(batchSize);
// Avoid giant stack allocations that can trigger StackOverflow when batchSize is large. // Avoid giant stack allocations that can trigger StackOverflow when batchSize is large.
// Rent a reusable array from the shared pool instead. // Rent a reusable array from the shared pool instead.
ulong[] orderArray = ArrayPool<ulong>.Shared.Rent(batchSize); ArrayPool<ulong> ulongPool = ThreadStaticPools.UlongPool;
ulong[] orderArray = ulongPool.Rent(batchSize);
UInt128 remaining; UInt128 remaining;
int currentSize; int currentSize;
int i; int i;
UInt128 q = UInt128.Zero; UInt128 q = UInt128.Zero;
try
{
while (kStart <= maxK && Volatile.Read(ref isPrime)) while (kStart <= maxK && Volatile.Read(ref isPrime))
{ {
remaining = maxK - kStart + UInt128.One; remaining = maxK - kStart + UInt128.One;
@@ -95,8 +97,8 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
smallCyclesView); smallCyclesView);
} }
orderBuffer.View.CopyToCPU(stream, ref MemoryMarshal.GetReference(orders), currentSize);
stream.Synchronize(); stream.Synchronize();
orderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(orders), currentSize);
if (_kernelType == GpuKernelType.Pow2Mod) if (_kernelType == GpuKernelType.Pow2Mod)
{ {
for (i = 0; i < currentSize; i++) for (i = 0; i < currentSize; i++)
@@ -122,13 +124,10 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
kStart += (UInt128)currentSize; kStart += (UInt128)currentSize;
} }
}
finally stream.Dispose();
{ ulongPool.Return(orderArray);
ArrayPool<ulong>.Shared.Return(orderArray);
orderBuffer.Dispose(); orderBuffer.Dispose();
execution.Dispose(); // GpuPrimeWorkLimiter.Release();
gpuLease.Dispose();
}
} }
} }
@@ -8,46 +8,129 @@ namespace PerfectNumbers.Core.Gpu;
public class MersenneNumberLucasLehmerGpuTester public class MersenneNumberLucasLehmerGpuTester
{ {
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>> KernelCache = new(); [ThreadStatic]
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ArrayView<GpuUInt128>>> AddSmallKernelCache = new(); private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>>? _lucasLehmerKernel;
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ArrayView<GpuUInt128>>> SubSmallKernelCache = new();
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ArrayView<GpuUInt128>>> ReduceKernelCache = new();
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>> IsZeroKernelCache = new();
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>>> BatchKernelCache = new();
private Action<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>> GetKernel(Accelerator accelerator) [ThreadStatic]
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>? _addSmallKernel;
[ThreadStatic]
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>? _subSmallKernel;
[ThreadStatic]
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>? _reduceKernel;
[ThreadStatic]
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>>? _isZeroKernel;
[ThreadStatic]
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>>>? _lucasLehmerBatchKernel;
private static Action<AcceleratorStream, Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>> GetLucasLehmerKernel(Accelerator accelerator)
{ {
return KernelCache.GetOrAdd(accelerator, accel => accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>(LucasLehmerKernels.Kernel)); var pool = _lucasLehmerKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
} }
private Action<Index1D, ulong, ArrayView<GpuUInt128>> GetAddSmallKernel(Accelerator accelerator) => var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>(LucasLehmerKernels.Kernel);
AddSmallKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.AddSmallKernel));
private Action<Index1D, ulong, ArrayView<GpuUInt128>> GetSubSmallKernel(Accelerator accelerator) => var kernel = KernelUtil.GetKernel(loaded);
SubSmallKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.SubtractSmallKernel));
private Action<Index1D, ulong, ArrayView<GpuUInt128>> GetReduceKernel(Accelerator accelerator) => cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>>();
ReduceKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.ReduceModMersenneKernel)); pool[accelerator] = cached;
return cached;
}
private Action<Index1D, ArrayView<GpuUInt128>, ArrayView<byte>> GetIsZeroKernel(Accelerator accelerator) => private static Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>> GetAddSmallKernel(Accelerator accelerator)
IsZeroKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>(LucasLehmerKernels.IsZeroKernel)); {
var pool = _addSmallKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
private Action<Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>> GetBatchKernel(Accelerator accelerator) => var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.AddSmallKernel);
BatchKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>>(LucasLehmerKernels.KernelBatch));
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>();
pool[accelerator] = cached;
return cached;
}
private static Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>> GetSubSmallKernel(Accelerator accelerator)
{
var pool = _subSmallKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.SubtractSmallKernel);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>();
pool[accelerator] = cached;
return cached;
}
private static Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>> GetReduceKernel(Accelerator accelerator)
{
var pool = _reduceKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.ReduceModMersenneKernel);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>();
pool[accelerator] = cached;
return cached;
}
private static Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<byte>> GetIsZeroKernel(Accelerator accelerator)
{
var pool = _isZeroKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>(LucasLehmerKernels.IsZeroKernel);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>>();
pool[accelerator] = cached;
return cached;
}
private static Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>> GetLucasLehmerBatchKernel(Accelerator accelerator)
{
var pool = _lucasLehmerBatchKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>>(LucasLehmerKernels.KernelBatch);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>>>();
pool[accelerator] = cached;
return cached;
}
// Configurable LL slice size to keep kernels short. Default 32. // Configurable LL slice size to keep kernels short. Default 32.
public int SliceSize = 32; public int SliceSize = 32;
public bool IsMersennePrime(ulong exponent)
{
// Default to global kernel preference for backward compatibility.
bool runOnGpu = !GpuContextPool.ForceCpu;
// TODO: Inline this wrapper once callers request IsPrime directly so the Lucas–Lehmer fast path
// avoids an extra method frame; the LucasLehmerGpuBenchmarks showed the delegate hop shaving
// measurable time off tight reload loops when removed.
return IsPrime(exponent, runOnGpu);
}
public bool IsPrime(ulong exponent, bool runOnGpu) public bool IsPrime(ulong exponent, bool runOnGpu)
{ {
// Early rejections aligned with incremental/order sieves, but safe for small p: // Early rejections aligned with incremental/order sieves, but safe for small p:
@@ -64,13 +147,11 @@ public class MersenneNumberLucasLehmerGpuTester
return false; return false;
} }
var limiter = GpuPrimeWorkLimiter.Acquire();
bool result; bool result;
if (exponent >= 128UL) if (exponent >= 128UL)
{ {
if (!TryGetNttParameters(exponent, out var nttMod, out var primitiveRoot)) if (!TryGetNttParameters(exponent, out var nttMod, out var primitiveRoot))
{ {
limiter.Dispose();
throw new NotSupportedException("NTT parameters for the given exponent are not available."); throw new NotSupportedException("NTT parameters for the given exponent are not available.");
} }
@@ -78,19 +159,22 @@ public class MersenneNumberLucasLehmerGpuTester
} }
else else
{ {
var gpu = GpuContextPool.RentPreferred(preferCpu: !runOnGpu); // GpuPrimeWorkLimiter.Acquire();
var accelerator = gpu.Accelerator; var acceleratorIndex = AcceleratorPool.Shared.Rent();
var kernel = GetKernel(accelerator); var accelerator = _accelerators[acceleratorIndex];
var stream = accelerator.CreateStream();
var modulus = new GpuUInt128(((UInt128)1 << (int)exponent) - 1UL); // TODO: Cache these Mersenne moduli per exponent so LL GPU runs skip rebuilding them every launch. var modulus = new GpuUInt128(((UInt128)1 << (int)exponent) - 1UL); // TODO: Cache these Mersenne moduli per exponent so LL GPU runs skip rebuilding them every launch.
var buffer = accelerator.Allocate1D<GpuUInt128>(1); var buffer = accelerator.Allocate1D<GpuUInt128>(1);
kernel(1, exponent, modulus, buffer.View); var lucasLehmerKernel = GetLucasLehmerKernel(accelerator);
accelerator.Synchronize(); lucasLehmerKernel(stream, 1, exponent, modulus, buffer.View);
stream.Synchronize();
// TODO: Replace this with .CopyToCPU with pre-allocated buffer using stream. Synchronize on the stream after copy.
result = buffer.GetAsArray1D()[0].IsZero; result = buffer.GetAsArray1D()[0].IsZero;
buffer.Dispose(); buffer.Dispose();
gpu.Dispose(); stream.Dispose();
} }
limiter.Dispose(); // GpuPrimeWorkLimiter.Release();
return result; return result;
} }
@@ -110,16 +194,18 @@ public class MersenneNumberLucasLehmerGpuTester
} }
} }
var gpu = GpuContextPool.RentPreferred(preferCpu: false); var acceleratorIndex = AcceleratorPool.Shared.Rent();
var accelerator = gpu.Accelerator; var accelerator = _accelerators[acceleratorIndex];
var kernel = GetBatchKernel(accelerator); var stream = accelerator.CreateStream();
var expBuffer = accelerator.Allocate1D<ulong>(count); var expBuffer = accelerator.Allocate1D<ulong>(count);
ulong[] expArray = ArrayPool<ulong>.Shared.Rent(count); ArrayPool<ulong> ulongPool = ThreadStaticPools.UlongPool;
ulong[] expArray = ulongPool.Rent(count);
exponents.CopyTo(expArray); exponents.CopyTo(expArray);
expBuffer.View.CopyFromCPU(ref expArray[0], count); expBuffer.View.CopyFromCPU(stream, ref expArray[0], count);
GpuUInt128[] modulusArray = ArrayPool<GpuUInt128>.Shared.Rent(count); ArrayPool<GpuUInt128> gpuUInt128Pool = ThreadStaticPools.GpuUInt128Pool;
GpuUInt128[] modulusArray = gpuUInt128Pool.Rent(count);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
// TODO: Replace this per-exponent shift with a small shared table (one entry per supported exponent < 128) // TODO: Replace this per-exponent shift with a small shared table (one entry per supported exponent < 128)
@@ -128,19 +214,20 @@ public class MersenneNumberLucasLehmerGpuTester
} }
var modBuffer = accelerator.Allocate1D<GpuUInt128>(count); var modBuffer = accelerator.Allocate1D<GpuUInt128>(count);
modBuffer.View.CopyFromCPU(ref modulusArray[0], count); modBuffer.View.CopyFromCPU(stream, ref modulusArray[0], count);
var stateBuffer = accelerator.Allocate1D<GpuUInt128>(count); var stateBuffer = accelerator.Allocate1D<GpuUInt128>(count);
kernel(count, expBuffer.View, modBuffer.View, stateBuffer.View); var lucasLehmerBatchKernel = GetLucasLehmerBatchKernel(accelerator);
accelerator.Synchronize(); lucasLehmerBatchKernel(stream, count, expBuffer.View, modBuffer.View, stateBuffer.View);
stateBuffer.View.CopyToCPU(ref residues[0], count); stateBuffer.View.CopyToCPU(stream, ref residues[0], count);
stream.Synchronize();
stateBuffer.Dispose(); stateBuffer.Dispose();
modBuffer.Dispose(); modBuffer.Dispose();
expBuffer.Dispose(); expBuffer.Dispose();
ArrayPool<GpuUInt128>.Shared.Return(modulusArray); gpuUInt128Pool.Return(modulusArray);
ArrayPool<ulong>.Shared.Return(expArray); ulongPool.Return(expArray);
gpu.Dispose(); stream.Dispose();
} }
public void IsMersennePrimeBatch(ReadOnlySpan<ulong> exponents, Span<bool> results) public void IsMersennePrimeBatch(ReadOnlySpan<ulong> exponents, Span<bool> results)
@@ -151,29 +238,27 @@ public class MersenneNumberLucasLehmerGpuTester
throw new ArgumentException("Result span too small", nameof(results)); throw new ArgumentException("Result span too small", nameof(results));
} }
GpuUInt128[] buffer = ArrayPool<GpuUInt128>.Shared.Rent(count); ArrayPool<GpuUInt128> gpuUInt128Pool = ThreadStaticPools.GpuUInt128Pool;
try GpuUInt128[] buffer = gpuUInt128Pool.Rent(count);
{
ComputeResidues(exponents, buffer.AsSpan(0, count)); ComputeResidues(exponents, buffer.AsSpan(0, count));
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
results[i] = buffer[i].IsZero; results[i] = buffer[i].IsZero;
} }
gpuUInt128Pool.Return(buffer);
} }
finally
{ private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
ArrayPool<GpuUInt128>.Shared.Return(buffer);
}
}
private bool IsMersennePrimeNtt(ulong exponent, GpuUInt128 nttMod, GpuUInt128 primitiveRoot, bool runOnGpu) private bool IsMersennePrimeNtt(ulong exponent, GpuUInt128 nttMod, GpuUInt128 primitiveRoot, bool runOnGpu)
{ {
var gpu = GpuContextPool.RentPreferred(preferCpu: !runOnGpu); var acceleratorIndex = AcceleratorPool.Shared.Rent();
var accelerator = gpu.Accelerator; var accelerator = _accelerators[acceleratorIndex];
var addKernel = GetAddSmallKernel(accelerator); var stream = accelerator.CreateStream();
var subKernel = GetSubSmallKernel(accelerator); var subKernel = GetSubSmallKernel(accelerator);
var reduceKernel = GetReduceKernel(accelerator); var reduceKernel = GetReduceKernel(accelerator);
var zeroKernel = GetIsZeroKernel(accelerator); var isZeroKernel = GetIsZeroKernel(accelerator);
int limbCount = (int)((exponent + 127UL) / 128UL); int limbCount = (int)((exponent + 127UL) / 128UL);
int target = limbCount * 2; int target = limbCount * 2;
@@ -183,8 +268,9 @@ public class MersenneNumberLucasLehmerGpuTester
transformLength <<= 1; transformLength <<= 1;
} }
var stateBuffer = accelerator.Allocate1D<GpuUInt128>(transformLength); var stateBuffer = accelerator.Allocate1D<GpuUInt128>(transformLength);
stateBuffer.MemSetToZero(); stateBuffer.MemSetToZero(stream);
addKernel(1, 4UL, stateBuffer.View); var addSmallKernel = GetAddSmallKernel(accelerator);
addSmallKernel(stream, 1, 4UL, stateBuffer.View);
// TODO(LL-SLICE): Slice Lucas–Lehmer iterations into short batches to // TODO(LL-SLICE): Slice Lucas–Lehmer iterations into short batches to
// avoid long-running kernels and TDR. Example: process 8–64 iterations // avoid long-running kernels and TDR. Example: process 8–64 iterations
@@ -199,27 +285,26 @@ public class MersenneNumberLucasLehmerGpuTester
for (int s = 0; s < iter; s++) for (int s = 0; s < iter; s++)
{ {
// Square in NTT domain with staged kernels (short runtime per kernel). // Square in NTT domain with staged kernels (short runtime per kernel).
NttGpuMath.SquareDevice(accelerator, stateBuffer.View, nttMod, primitiveRoot); NttGpuMath.SquareDevice(accelerator, stream, stateBuffer.View, nttMod, primitiveRoot);
subKernel(1, 2UL, stateBuffer.View); subKernel(stream, 1, 2UL, stateBuffer.View);
reduceKernel(1, exponent, stateBuffer.View); reduceKernel(stream, 1, exponent, stateBuffer.View);
} }
// Synchronize between slices to yield to scheduler and avoid TDR. // Synchronize between slices to yield to scheduler and avoid TDR.
accelerator.Synchronize();
i += (ulong)iter; i += (ulong)iter;
} }
reduceKernel(1, exponent, stateBuffer.View); reduceKernel(stream, 1, exponent, stateBuffer.View);
var resultBuffer = accelerator.Allocate1D<byte>(1); var resultBuffer = accelerator.Allocate1D<byte>(1);
zeroKernel(1, stateBuffer.View, resultBuffer.View); isZeroKernel(stream, 1, stateBuffer.View, resultBuffer.View);
accelerator.Synchronize(); Span<byte> result = stackalloc byte[1];
byte[] result = new byte[1]; resultBuffer.View.CopyToCPU(stream, result);
resultBuffer.View.CopyToCPU(result); stream.Synchronize();
bool isPrime = result[0] != 0; bool isPrime = result[0] != 0;
resultBuffer.Dispose(); resultBuffer.Dispose();
stateBuffer.Dispose(); stateBuffer.Dispose();
gpu.Dispose(); stream.Dispose();
return isPrime; return isPrime;
} }
@@ -488,6 +573,4 @@ public class MersenneNumberLucasLehmerGpuTester
return result; return result;
} }
} }
@@ -7,36 +7,46 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO
private readonly GpuKernelType _kernelType = kernelType; private readonly GpuKernelType _kernelType = kernelType;
private readonly bool _useGpuOrder = useGpuOrder; private readonly bool _useGpuOrder = useGpuOrder;
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
{ {
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); // GpuPrimeWorkLimiter.Acquire();
var execution = gpuLease.EnterExecutionScope(); // var accelerator = SharedGpuContext.Accelerator;
var accelerator = gpuLease.Accelerator; var acceleratorIndex = AcceleratorPool.Shared.Rent();
var stream = gpuLease.Stream; var accelerator = _accelerators[acceleratorIndex];
var stream = accelerator.CreateStream();
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR
UInt128 kStart = UInt128.One; UInt128 kStart = UInt128.One;
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL; ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters
KernelContainer kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.Pow2ModOrderKernelScan | KernelType.IncrementalOrderKernelScan);
var kernel = _kernelType switch var kernel = _kernelType switch
{ {
GpuKernelType.Pow2Mod => gpuLease.Pow2ModOrderKernel, GpuKernelType.Pow2Mod => kernels.Pow2ModOrder!,
_ => gpuLease.IncrementalOrderKernel, _ => kernels.IncrementalOrder!,
}; };
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. MemoryBuffer1D<int, ILGPU.Stride1D.Dense>? foundBuffer;
exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3); // lock (accelerator)
try
{ {
foundBuffer = accelerator.Allocate1D<int>(1); // TODO: Replace this allocation with the pooled device buffer from GpuOrderKernelBenchmarks so repeated scans reuse the pinned staging memory instead of allocating per run.
}
exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
UInt128 remaining; UInt128 remaining;
int currentSize; int currentSize;
int found = 0; int found = 0;
var smallCyclesView = GpuKernelPool.GetSmallCyclesOnDevice(kernels);
while (kStart <= maxK && Volatile.Read(ref isPrime)) while (kStart <= maxK && Volatile.Read(ref isPrime))
{ {
remaining = maxK - kStart + 1UL; remaining = maxK - kStart + 1UL;
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining; currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
// This clear is required because the result may not always be set. Remove it after the kernels are modified to always set the result. // This clear is required because the result may not always be set. Remove it after the kernels are modified to always set the result.
foundBuffer.MemSetToZero(); foundBuffer.MemSetToZero(stream);
// Precompute residue automaton bases for this batch // Precompute residue automaton bases for this batch
UInt128 q0 = twoP * kStart + 1UL; UInt128 q0 = twoP * kStart + 1UL;
// TODO: Replace these modulo recomputations with the divisor-cycle stepping deltas captured in // TODO: Replace these modulo recomputations with the divisor-cycle stepping deltas captured in
@@ -45,14 +55,14 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3); q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5); var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
// Ensure device has small cycles table for early rejections in order kernels // Ensure device has small cycles table for early rejections in order kernels
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); kernel(
kernel(stream, currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, divMul, stream, currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart,
kernelArgs, foundBuffer.View, smallCyclesView); last, divMul, kernelArgs, foundBuffer.View, smallCyclesView
);
foundBuffer.View.CopyToCPU(stream, ref found, 1);
stream.Synchronize(); stream.Synchronize();
foundBuffer.View.CopyToCPU(ref found, 1);
if (found != 0) if (found != 0)
{ {
Volatile.Write(ref isPrime, false); Volatile.Write(ref isPrime, false);
@@ -61,13 +71,9 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO
kStart += (UInt128)currentSize; kStart += (UInt128)currentSize;
} }
}
finally
{
foundBuffer.Dispose();
execution.Dispose();
gpuLease.Dispose();
}
stream.Dispose();
foundBuffer.Dispose();
// GpuPrimeWorkLimiter.Release();
} }
} }
Loaded 100 of 135 files, more files were not shown because too many files have changed in this diff. Show more