219 lines
9.7 KiB
Markdown
219 lines
9.7 KiB
Markdown
# License
|
||
You're allowed to use the source code for education purposes, only.
|
||
* You can download it,
|
||
* You can review it,
|
||
* You can run it to learn how it works,
|
||
* You can run it to learn if it works,
|
||
* You can use it for learning how certain operations can be done, optimized etc.,
|
||
* You can use it to search and announce new perfect numbers, but please always include the information that you used my scanner. I have family and I need to bring money to the house.
|
||
|
||
\
|
||
What I'm asking you **not** to do, without license:
|
||
|
||
* You're **not** allowed to copy / paste or integrate my code and / or assemblies with your code.
|
||
* You're **not** allowed to replicate it 1 : 1 in your project. Please write your own implementation with the understanding how it works. We may end up with the same solution.
|
||
|
||
|
||
### **Please play fair and don't use it to claim your success in the area.**
|
||
\
|
||
\
|
||
Any other use requires the payment of the licensing fee, which can be done using the links below. The payment is one-time payment and includes all future updates, if any.
|
||
|
||
In case of any concerns or questions, please feel free to reach out using my [LinkedIn account](https://www.linkedin.com/in/marek-lemanczyk-432819154).
|
||
|
||
|
||
|
||
* Personal License - $10
|
||
https://buymeacoffee.com/mlemanczyk/e/455507
|
||
|
||
* Earning / Commercial License - $100
|
||
https://buymeacoffee.com/mlemanczyk/e/455508
|
||
|
||
[](https://www.buymeacoffee.com/mlemanczyk)
|
||
|
||
# Thirdparty Components
|
||
The source code relays on selected functionality from the following packages:
|
||
* [FluentAssertions](https://www.nuget.org/packages/FluentAssertions)
|
||
* [ILGPU](https://www.nuget.org/packages/ILGPU)
|
||
* [ILGPU.Algorithms](https://www.nuget.org/packages/ILGPU.Algorithms)
|
||
* [Open.Numeric.Primes](https://www.nuget.org/packages/Open.Numeric.Primes)
|
||
* [PeterO.Numbers](https://www.nuget.org/packages/PeterO.Numbers)
|
||
* [xunit](https://www.nuget.org/packages/xunit)
|
||
|
||
# Description
|
||
|
||
This document explains **what the program does**, **how it works step-by-step**, and **why** each optimization exists. It reflects the *current* code paths in `Program.cs` and the helper library (`PerfectNumbers.Core`), describing only what the code actually does (comments ignored).
|
||
|
||
# Supported modes
|
||
* `--mersenne=pow2mod`
|
||
* `--mersenne=incremental`
|
||
* `--mersenne=lucas`
|
||
* `--mersenne=residue`
|
||
|
||
# EvenPerfectBitScanner – Configuration and Pipeline
|
||
Please run `EvenPerfectBitScanner.exe /?` to display all command line parameters with a short description. The description below describes `--mersenne=residue` mode.
|
||
|
||
## What the exact CLI config does
|
||
|
||
```bash
|
||
./EvenPerfectBitScanner.exe
|
||
--increment=add
|
||
--mersenne=residue
|
||
--threads=1024
|
||
--primes-device=gpu
|
||
--mersenne-device=gpu
|
||
--block-size=10000000
|
||
--gpu-prime-threads=1024
|
||
--write-batch-size=10000
|
||
--gpu-prime-batch=512000
|
||
````
|
||
|
||
* **Candidate stepping**:
|
||
`--increment=add` → the producer uses `TransformPAdd` (see my `Program.cs`), which locks into `p ≡ 1 or 5 (mod 6)`.
|
||
|
||
* **Prime tests (p)**:
|
||
`--primes-device=gpu` → `PrimeTester` runs a GPU-batched small-prime sieve (and light CPU checks) (`PrimeTester.cs`).
|
||
|
||
* **Mersenne stage**:
|
||
`--mersenne=residue` + `--mersenne-device=gpu` → `MersenneNumberTester` selects the residue divisor path and routes its scanning kernels to GPU (`MersenneNumberTester.cs` + `Gpu/MersenneNumberResidueGpuTester.cs`). We do not run Lucas–Lehmer in this config.
|
||
|
||
* **GPU tuning**:
|
||
`--gpu-prime-threads=1024` gates concurrent GPU prime work via `GpuPrimeWorkLimiter`.
|
||
`--gpu-prime-batch=512000` sets the batch size in `PrimeTester`.
|
||
|
||
* **I/O**:
|
||
`--write-batch-size=10000` batches CSV writes.
|
||
|
||
---
|
||
|
||
## The pipeline, line-by-line logic of each stage (and every early rejection)
|
||
|
||
I’ll walk you through what happens for a single candidate exponent `p`. The exact code paths below are the ones used in my run.
|
||
|
||
### Candidate acquisition (producer)
|
||
|
||
* We atomically reserve the next `p` using a packed `ulong` CAS over `{p << 3 | remainder}` (my `_state`). That’s the lock-free distributor in `Program.cs`.
|
||
|
||
* **TransformPAdd**:
|
||
First step normalizes to `p % 6 ∈ {1,5}` and after that it fixes remainders to 1 or 5 with each increase.
|
||
|
||
### Cheap filters on p
|
||
|
||
(All in `PrimeTester.cs` unless noted)
|
||
|
||
* **Trivial rejects**:
|
||
|
||
* `n <= 3`: accept 2 and 3 only.
|
||
* even `n`: reject.
|
||
* `n % 5 == 0`: reject quickly.
|
||
|
||
* **“Ends-with-1” GCD heuristic**:
|
||
If `n % 10 == 1` and `SharesFactorWithMaxExponent(n)` → reject.
|
||
|
||
`SharesFactorWithMaxExponent(n)` computes `m = floor(log2 n)` and rejects if `gcd(n, m) ≠ 1`.
|
||
There’s also a GPU batch form `SharesFactorWithMaxExponentBatch` used elsewhere when screening arrays.
|
||
|
||
*Why it’s valid*: numbers of the form `10t+1` have specific small-factor structure; combining that with `gcd(n, floor(log2 n))` cheaply kills many composites without MR. (This is a heuristic prefilter based on my analysis of primes ending with 1 or 7 and testing; it won’t reject primes incorrectly.)
|
||
|
||
* **Small-prime sieve (CPU loop, still inside PrimeTester)**:
|
||
Iterate `PrimesGenerator.SmallPrimes` while `prime^2 ≤ n` using the ready-made `SmallPrimesPow2` to avoid sqrt. Reject immediately if divisible.
|
||
|
||
With `--primes-device=gpu`, we also have a device-side sieve for batches (`IsPrimeGpuBatch`): it copies chunks to the device, runs a simple sieve kernel over an uploaded small-primes table, and copies flags back. My per-candidate path uses the single-value version, but the machinery (stateful kernel cache, scratch buffers) is there for vectorized checks.
|
||
|
||
➡ If `p` fails any of the above → we never touch `M_p`. That’s my first wall of early rejections.
|
||
|
||
### Mersenne stage
|
||
|
||
(All driven by `MersenneNumberTester.cs`)
|
||
|
||
* **Hard mathematical pre-rejects (top of IsMersennePrime)**:
|
||
|
||
* If `p % 3 == 0` and `p != 3` → reject (since `7 ∣ M_p` when `3 ∣ p`).
|
||
* If `(p & 3) == 1` and `p.SharesFactorWithExponentMinusOne()` → reject.
|
||
|
||
* `SharesFactorWithExponentMinusOne` (in `ULongExtensions.cs`) factors `p-1` by trial over `SmallPrimes`, removes multiplicities, and for each distinct prime `r` checks whether the multiplicative order of 2 modulo `r` divides `p` (via `CalculateOrder()`), also handling the leftover cofactor. If any such order divides `p`, we can certify a small factor for `M_p` and reject `p` outright.
|
||
* If `p` is even → reject (trivial, here `p` is already odd from the prime screen, but it’s kept for safety).
|
||
|
||
* **Residue divisor scanning (`_useResidue == true`)**:
|
||
|
||
* We set `twoP = 2*p`. Valid Mersenne divisors have the form `q = 2kp + 1, k ≥ 1` with congruence constraints `q ≡ 1 (mod 2p)` and `q ≡ 1 or 7 (mod 8)`.
|
||
* **Digit filter**:
|
||
|
||
* Compute `lastIsSeven = ((p & 3) == 3)` which encodes the last decimal digit of `M_p = 2^p - 1`.
|
||
|
||
* If last digit of `M_p` is 7 → we allow only `q` with last digit in `{3, 7, 9}`.
|
||
|
||
* Else (last digit 1) → we allow only `{1, 3, 7, 9}`.
|
||
This is implemented both on CPU (`CpuConstants.LastSevenMask10`, `LastOneMask10`) and GPU by calculating `q % 10` incrementally as `k` grows.
|
||
|
||
* **Rolling residues**: we keep four rolling residues for each lane: `q mod 10, 8, 5, 3`. That allows immediate rejection of `q` divisible by 2, 3, or 5 without any division (just adds, bit-ands and subtracts).
|
||
|
||
* **CPU residue scanner**: uses `ModResidueTracker` with update formula `r′ ≡ 2^Δ⋅r+(2^Δ−1) (mod d)`.
|
||
|
||
* **GPU residue scanner**: batches `k`, evaluates residues, prunes with cycles table, runs mod-pow on survivors. Uses pooled contexts and kernel caches.
|
||
|
||
* **Short-circuit**: as soon as we find any valid divisor `q | M_p` in the scanned window, we stop.
|
||
|
||
➡ If scanning completes up to `k 2^p / 2 + 1` and no divisor was found → `IsMersennePrime(p)` returns true and the CSV line has `isPerfect=true`. This means that `p` passed all the tests and is a **candidate** for constructing a perfect number, which requires testing with other methods.
|
||
|
||
---
|
||
|
||
## What we cache, pool, and batch (and why)
|
||
|
||
* **Per-thread singletons**: `PrimeTester`, `MersenneNumberTester`, `ModResidueTracker` → avoid hot locks, reuse GPU/CPU state.
|
||
|
||
* **Mod residue tracker**: avoids recomputing residues from scratch, supports incremental updates.
|
||
|
||
* **Divisor cycles**: persistent, precomputed orders for `divisors ≤ 4,000,000`, shared singleton, uploaded to GPU.
|
||
|
||
* **GPU infra**: pooled contexts/kernels, concurrency gating.
|
||
|
||
* **I/O pooling**: `StringBuilderPool` + batched writes.
|
||
|
||
---
|
||
|
||
## Every mathematical rule that we’re using to reject early
|
||
|
||
* Only prime exponents matter.
|
||
* 7 divides `M_p` if `3 ∣ p`.
|
||
* Order-based rejections using factors of `p−1`.
|
||
* Shape of Mersenne divisor: `q=2kp+1`, `q≡1 or 7 (mod 8)`.
|
||
* Cycle length filter for small `divisors`.
|
||
* Monotone residue stepping with closed forms.
|
||
|
||
---
|
||
|
||
## Hot-path micro-optimizations already in the code
|
||
|
||
* Lock-free work distribution.
|
||
* Per-thread testers & trackers.
|
||
* Sqrt-free stopping in sieves.
|
||
* Device-side constant tables.
|
||
|
||
---
|
||
|
||
## Risks, correctness gaps, and performance pitfalls (with precise fixes)
|
||
|
||
* **Per-thread buffers are huge**: `--block-size=10_000_000` → \~80 GB at 1024 threads.
|
||
|
||
* **1024 CPU threads + 1024 GPU permits** oversubscribe virtually any GPU. When running such configurations, make sure that you have enough RAM or swap file.
|
||
|
||
* How: set `--threads` to `Environment.ProcessorCount` and `--gpu-prime-threads` to \~8–32.
|
||
|
||
---
|
||
|
||
## “Full accounting” of what gets computed, rejected, cached, or pooled
|
||
|
||
### Per p
|
||
|
||
* **Computed**: next `p`, small-prime residues, heuristic gcd if ending in 1, trial division.
|
||
* **Rejected**: trivial composite checks; small-prime hits.
|
||
* **If p prime**: pre-rules, order checks, residue scan inputs, k-batches.
|
||
|
||
### Cached/pooled
|
||
|
||
* **Thread scope**: testers, trackers, GPU contexts, buffers.
|
||
* **Process scope**: divisor cycles dataset, small primes.
|
||
* **IO**: pooled `StringBuilder` + batched writes.
|
||
|
||
--- |