diff --git a/README.md b/README.md index d0b3037..86ffa7c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,188 @@ -# even-perfect-numbers-scanner -TBD +# License +You're allowed to use the source code for education purposes, only. Any other use requires the payment of the licensing fee, which can be done using the links below. + +* 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) + +# 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. + +--- \ No newline at end of file diff --git a/buy-me-a-coffee.svg b/buy-me-a-coffee.svg new file mode 100644 index 0000000..e5b676f --- /dev/null +++ b/buy-me-a-coffee.svg @@ -0,0 +1 @@ +Buy me a coffeeBuy me a coffee \ No newline at end of file