Files
even-perfect-numbers-scanner/README.md
T
2025-09-16 11:44:56 +02:00

219 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
[![Donate](buy-me-a-coffee.svg)](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.
---