2025-10-08 19:57:10 +02:00
2025-10-17 02:40:51 +02:00
2025-10-17 02:40:51 +02:00
2025-09-21 05:30:49 +02:00
2025-10-07 22:02:15 +02:00
2025-09-09 14:49:06 +02:00
2025-09-16 11:44:56 +02:00
2025-10-07 22:01:40 +02:00

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.

Donate

Thirdparty Components

The source code relays on selected functionality from the following packages:

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

./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.

S
Description
No description provided
Readme
63 MiB
0 Stars 1 Watchers 0 Forks
Languages
C# 100%