Performance-focused Lucas–Lehmer primality test for Mersenne numbers, with tiered backends, multi-mode operation, and a profiling/benchmark harness.
A Mersenne number with prime exponent
Define the sequence
Theorem (Lucas–Lehmer): For an odd prime
Each Lucas–Lehmer iteration computes the recurrence modulo
The modular reduction exploits the Mersenne property: because
with at most one further subtraction of
Each Lucas–Lehmer iteration computes s = (s² − 2) mod (2^p − 1).
The backend is chosen automatically based on the exponent size:
| Exponent range | Backend | Algorithm |
|---|---|---|
p < 128 |
GenericBackend |
boost::multiprecision::cpp_int – reference path |
128 ≤ p < LL_LIMB_FFT_CROSSOVER (default 4000) |
LimbBackend |
True Comba schoolbook squaring + recursive Karatsuba + Mersenne fold |
p ≥ LL_LIMB_FFT_CROSSOVER |
FftMersenneBackend |
Crandall–Bailey DWT/FFT cyclic convolution |
The crossover threshold can be tuned at runtime via the LL_LIMB_FFT_CROSSOVER
environment variable (see Environment variables).
- Real-FFT optimisation (Phase 4): two n-point complex FFTs replaced by two n/2-point FFTs + O(n) pre/post-processing — ~46 % fewer butterflies per iteration.
- Fused hot op:
square_sub2_mod_mersenne()performs the full LL step in a single FFT pair; no intermediate allocation. - Pre-computed tables: twiddle factors, DWT weights, bit-reversal table, and digit-width table are allocated once per engine lifetime.
- No heap allocation inside the hot loop: all scratch buffers are pre-allocated.
- True Comba squaring: n*(n+1)/2 multiplications vs n² for generic multiply.
- Multiply-by-reciprocal carry propagation (Phase 3): avoids the 20+ cycle integer divide.
- Hardware sqrt for trial division (Phase 1): one
VSQRTSDbefore the loop, plain 64-bit comparison inside.
make all # release binary: bin/bignum
make plan-tool # C plan helper: bin/split_bucket_batches
Compiler flags (defaults):
-std=c++20 -O3 -march=native -mtune=native -flto -pthread
Dependencies: g++, make, libboost-dev
(sudo apt-get install -y g++ make libboost-dev pkg-config)
./bin/bignum [start_index] [threads] [progress]
| Argument | Default | Description |
|---|---|---|
start_index |
0 |
First index into the known Mersenne-prime exponent list |
threads |
max available cores | Worker thread count; 0 = max |
progress |
off | Any third argument enables per-iteration progress output |
The binary supports several operating modes selected via environment variables:
LL_SWEEP_MODE |
Mode | Description |
|---|---|---|
| (not set) | Benchmark / known-list | Walk known_mersenne_prime_exponents[] from start_index; is_prime_exponent() check skipped by default |
n |
Natural sweep | All integers in [LL_MIN_EXPONENT, LL_MAX_EXPONENT] |
p |
Prime sweep | All prime exponents in [LL_MIN_EXPONENT, LL_MAX_EXPONENT] |
m |
Mersenne-first sweep | Known Mersenne-prime exponents first, then remaining primes in range |
discover |
Discover mode | Explore primes beyond the known list; writes results to LL_OUTPUT_DIR |
power_bucket_primes |
Power-bucket mode | Sweep one power-of-two exponent bucket (B_n = [2^(n-1), 2^n−1]) |
| Variable | Default | Description |
|---|---|---|
LL_LIMB_FFT_CROSSOVER |
4000 |
Exponent threshold where LimbBackend gives way to FftMersenneBackend |
LL_STOP_AFTER_ONE |
0 |
1 = run exactly one exponent then exit |
LL_BENCHMARK_MODE |
1 for known-list/prime/Mersenne modes; 0 for natural mode |
Skip is_prime_exponent() check (safe when the input list is already prime) |
LL_MAX_EXPONENT_INDEX |
(list length) | Exclusive upper bound on the known-list index (used to keep CI bounded) |
LL_BENCH_OUTPUT |
(none) | Path for machine-readable CSV output (p,is_prime,time_sec) |
| Variable | Default | Description |
|---|---|---|
LL_SWEEP_MODE |
(none) | Operating mode: n, p, m, discover, power_bucket_primes |
LL_MIN_EXPONENT |
2 |
Lower bound (inclusive) for sweep modes |
LL_MAX_EXPONENT |
last known Mersenne exponent | Upper bound (inclusive) for sweep modes |
LL_MAX_CASES |
(no limit) | Hard cap on exponents to process |
LL_SHARD_INDEX |
0 |
Which shard to run (0-based) |
LL_SHARD_COUNT |
1 |
Total number of shards (for parallel CI jobs) |
LL_REVERSE_ORDER |
0 |
1 = reverse work list before running |
LL_LARGEST_FIRST |
0 |
1 = sort work list largest-exponent-first |
LL_THREADS |
(argv[2] or max) | Override thread count via environment |
| Variable | Default | Description |
|---|---|---|
LL_SINGLE_EXPONENT |
0 |
Explicit first exponent to test (0 = none) |
LL_MIN_EXPONENT |
136279841 |
Lower bound exclusive for discover range |
LL_MAX_EXPONENT |
200000000 |
Upper bound inclusive for discover range |
LL_STOP_AFTER_N_CASES |
0 |
Stop after N exponents (0 = no limit) |
LL_DRY_RUN |
0 |
1 = print plan without running LL tests |
LL_PROGRESS |
0 |
1 = show per-iteration LL progress |
LL_OUTPUT_DIR |
discover_out/ |
Directory for result files (CSV, JSON) |
| Variable | Default | Description |
|---|---|---|
LL_BUCKET_N |
0 |
Bucket number to run (1–64; 0 = all buckets from LL_BUCKET_START to LL_BUCKET_END) |
LL_BUCKET_START |
1 |
First bucket (inclusive) when LL_BUCKET_N=0 |
LL_BUCKET_END |
64 |
Last bucket (inclusive) when LL_BUCKET_N=0 |
LL_MAX_EXPONENTS_PER_JOB |
0 |
Cap on exponents per bucket (0 = no limit) |
LL_RESUME_FROM_EXPONENT |
0 |
Skip exponents below this value |
LL_STOP_AFTER_FIRST_PRIME_RESULT |
0 |
Stop after the first new Mersenne prime found |
| Variable | Default | Description |
|---|---|---|
LL_PROGRESS_INTERVAL_ITERS |
10000 |
Checkpoint every N iterations |
LL_PROGRESS_INTERVAL_SECONDS |
0 |
Also checkpoint every N seconds (0 = off) |
| Target | Description |
|---|---|
make all |
Build release binary bin/bignum |
make plan-tool |
Build C plan helper bin/split_bucket_batches |
make unit |
Run unit / correctness tests (fast) |
make smoke |
Run a single tiny exponent to confirm the binary works |
make regression |
Run bounded regression: exponents[0..26], p ≤ 44 497 (< 15 s) |
make test |
Composite: unit + smoke + regression |
make bench |
Interactive benchmark: 1 thread vs max cores (starts at index 14) |
make bench-ci |
Bounded CI benchmark (indices 14–26); writes bin/bench_ci_*.csv |
make prof |
Build profiling binary and run gprof; output → prof_report.txt |
make discover |
Run discover mode (set env vars to control behaviour) |
make discover-dry-run |
Print discover plan without running tests |
make bucket |
Run power-bucket prime sweep |
make bucket-dry-run |
Print bucket plan without running tests |
make manual-sweep |
Configurable sweep via environment variables |
make clean |
Remove bin/, prof_report.txt, gmon.out, discover_out/ |
Useful overrides:
make bench BENCH_START_INDEX=14 # start benchmark at index 14 (p=9689)
make discover LL_MAX_EXPONENT=150000000 LL_SHARD_COUNT=4 LL_SHARD_INDEX=0
make bucket LL_BUCKET_N=5make test # unit + smoke + bounded regression
make unit # unit tests only (< 1 s)
make smoke # single-exponent smoke test
make regression # bounded correctness run (< 15 s on a 2-core runner)The test binary is compiled from tests/test_bignum.cpp, which includes
src/BigNum.cpp with BIGNUM_NO_MAIN defined.
make prof # builds bin/bignum_prof (-O2 -pg), runs gprof, writes prof_report.txtThe profiling binary uses -O2 -pg (keeps enough optimisation to be
representative while preserving call structure for gprof).
make bench # 1 thread vs max cores, index 14 → end
make bench BENCH_START_INDEX=20 # start later in the list
make bench-ci # CI-safe subset (indices 14–26, writes CSV)
scripts/run_benchmark.sh [CSV] [start] [max_index] [threads]All workflows are in .github/workflows/.
| Job | Description |
|---|---|
unit-smoke |
Build + unit tests + smoke test (timeout: 10 min) |
regression |
Bounded regression: exponents[0..26] (timeout: 15 min) |
profile |
Build profiling binary, run gprof, upload prof_report.txt artifact |
bench-ci |
Optional — bounded benchmark CSV; only when workflow_dispatch with run_benchmark=true |
| File | Trigger | Description |
|---|---|---|
discover-primes.yml |
workflow_dispatch |
Run discover mode; configurable via inputs |
power-range-prime-sweep.yml |
workflow_dispatch |
Power-bucket sweep for one or more buckets |
prime-sweep-wrapper.yml |
workflow_dispatch |
Wrapper that fans out prime-sweep jobs |
manual-sweep.yml |
workflow_dispatch |
Ad-hoc configurable sweep |
| Script | Description |
|---|---|
scripts/run_benchmark.sh |
Run bounded benchmark, write CSV with thread column |
scripts/run_discovery_mode.sh |
Local mirror of the discover-mode workflow |
scripts/run_bucket.sh |
Run one power-of-two bucket sweep |
scripts/split_bucket_batches.py |
Python plan generator (superseded by bin/split_bucket_batches) |
scripts/generate_bucket_primes.py |
Enumerate prime exponents in a bucket |
scripts/generate_exponent_set.py |
Generate a custom exponent set |
scripts/merge_bucket_results.py |
Merge per-bucket result files |
The binary ships with 52 known Mersenne-prime exponents (M_2 through M_136279841).
In default benchmark mode the known list is used directly and
is_prime_exponent() is skipped.
Use LL_MAX_EXPONENT_INDEX to restrict how far into the list CI runs
(e.g. LL_MAX_EXPONENT_INDEX=27 stops before M_86243 to keep CI under 15 minutes).