Readme added

This commit is contained in:
Marek Lemańczyk committed 2025-09-09 14:49:06 +02:00
1 parent 3ae0348d24
commit e5b0fea18c
2 files changed
+189 -2

No files matched your search

+188 -2
View File
@@ -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.
---
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="236" height="20" role="img" aria-label="Buy me a coffee"><title>Buy me a coffee</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="236" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="137" height="20" fill="#555"/><rect x="137" width="99" height="20" fill="#4c1"/><rect width="236" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="695" y="150" fill="#010101" transform="scale(.1)" textLength="auto"><a href="https://www.buymeacoffee.com/mlemanczyk">Buy me a coffee</a></text><text x="695" y="140" transform="scale(.1)" fill="#fff" textLength="auto"><a href="https://www.buymeacoffee.com/mlemanczyk"/></text><text aria-hidden="true" x="705" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="890">Buy me a coffee</text><text x="695" y="142" transform="scale(.1)" fill="#fff" textLength="auto"><a href="https://www.buymeacoffee.com/mlemanczyk">Buy me a coffee</a></text><text aria-hidden="true" x="1890" y="142" fill="#010101" transform="scale(.1)" textLength="auto"><a href="https://www.buymeacoffee.com/mlemanczyk">$10 / $100</a></text></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB