diff --git a/tests/beman/big_int/perf/README.md b/tests/beman/big_int/perf/README.md
index 5ddd0d8f..02e97b2d 100644
--- a/tests/beman/big_int/perf/README.md
+++ b/tests/beman/big_int/perf/README.md
@@ -15,3 +15,6 @@ The Google Benchmark suite is part of the CMake build (enable it with
`-DBEMAN_BIG_INT_BUILD_BENCHMARKS=ON`, or use a `*-release-benchmarks` preset);
the `*.perf.cpp` and `*.limbs.cpp` programs are standalone and depend on
Boost.Multiprecision and GMP, so they are compiled by hand.
+
+The GMP gap analysis (where `big_int` is slower or faster than GMP, per operation and size band, with flame graphs
+and a ranked optimization list) is in [`gmp_gap_analysis.md`](gmp_gap_analysis.md).
diff --git a/tests/beman/big_int/perf/flamegraphs/add_1000x1000_auto.svg b/tests/beman/big_int/perf/flamegraphs/add_1000x1000_auto.svg
new file mode 100644
index 00000000..5d61ccea
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/add_1000x1000_auto.svg
@@ -0,0 +1,650 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/add_1000x1000_gmpz.svg b/tests/beman/big_int/perf/flamegraphs/add_1000x1000_gmpz.svg
new file mode 100644
index 00000000..6e873f5d
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/add_1000x1000_gmpz.svg
@@ -0,0 +1,494 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/add_1000x1000_inplace.svg b/tests/beman/big_int/perf/flamegraphs/add_1000x1000_inplace.svg
new file mode 100644
index 00000000..a99bea8c
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/add_1000x1000_inplace.svg
@@ -0,0 +1,522 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/add_16x16_auto.svg b/tests/beman/big_int/perf/flamegraphs/add_16x16_auto.svg
new file mode 100644
index 00000000..e25b98e7
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/add_16x16_auto.svg
@@ -0,0 +1,690 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/add_16x16_gmpz.svg b/tests/beman/big_int/perf/flamegraphs/add_16x16_gmpz.svg
new file mode 100644
index 00000000..3eebef3d
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/add_16x16_gmpz.svg
@@ -0,0 +1,494 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/divrem_131072x65536_auto.svg b/tests/beman/big_int/perf/flamegraphs/divrem_131072x65536_auto.svg
new file mode 100644
index 00000000..c0b52d62
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/divrem_131072x65536_auto.svg
@@ -0,0 +1,2782 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/divrem_131072x65536_gmpz.svg b/tests/beman/big_int/perf/flamegraphs/divrem_131072x65536_gmpz.svg
new file mode 100644
index 00000000..fa15a53c
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/divrem_131072x65536_gmpz.svg
@@ -0,0 +1,2394 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/divrem_256x128_auto.svg b/tests/beman/big_int/perf/flamegraphs/divrem_256x128_auto.svg
new file mode 100644
index 00000000..9d97f36a
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/divrem_256x128_auto.svg
@@ -0,0 +1,790 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/divrem_256x128_gmpz.svg b/tests/beman/big_int/perf/flamegraphs/divrem_256x128_gmpz.svg
new file mode 100644
index 00000000..f0de1079
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/divrem_256x128_gmpz.svg
@@ -0,0 +1,622 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/divrem_4096x2048_auto.svg b/tests/beman/big_int/perf/flamegraphs/divrem_4096x2048_auto.svg
new file mode 100644
index 00000000..841dddf8
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/divrem_4096x2048_auto.svg
@@ -0,0 +1,2206 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/divrem_4096x2048_gmpz.svg b/tests/beman/big_int/perf/flamegraphs/divrem_4096x2048_gmpz.svg
new file mode 100644
index 00000000..ca928d7a
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/divrem_4096x2048_gmpz.svg
@@ -0,0 +1,2530 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/fromchars_10000x10_auto.svg b/tests/beman/big_int/perf/flamegraphs/fromchars_10000x10_auto.svg
new file mode 100644
index 00000000..eaf34c07
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/fromchars_10000x10_auto.svg
@@ -0,0 +1,1294 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/fromchars_10000x10_gmpz.svg b/tests/beman/big_int/perf/flamegraphs/fromchars_10000x10_gmpz.svg
new file mode 100644
index 00000000..2a291404
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/fromchars_10000x10_gmpz.svg
@@ -0,0 +1,2078 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/gcd_1024x1024_auto.svg b/tests/beman/big_int/perf/flamegraphs/gcd_1024x1024_auto.svg
new file mode 100644
index 00000000..d8aed622
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/gcd_1024x1024_auto.svg
@@ -0,0 +1,586 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/gcd_1024x1024_gmpz.svg b/tests/beman/big_int/perf/flamegraphs/gcd_1024x1024_gmpz.svg
new file mode 100644
index 00000000..f50452db
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/gcd_1024x1024_gmpz.svg
@@ -0,0 +1,2198 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/mul_1024x1024_auto.svg b/tests/beman/big_int/perf/flamegraphs/mul_1024x1024_auto.svg
new file mode 100644
index 00000000..269dfcad
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/mul_1024x1024_auto.svg
@@ -0,0 +1,806 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/mul_1024x1024_gmpz.svg b/tests/beman/big_int/perf/flamegraphs/mul_1024x1024_gmpz.svg
new file mode 100644
index 00000000..fc419e27
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/mul_1024x1024_gmpz.svg
@@ -0,0 +1,702 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/mul_16384x16384_auto.svg b/tests/beman/big_int/perf/flamegraphs/mul_16384x16384_auto.svg
new file mode 100644
index 00000000..815200af
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/mul_16384x16384_auto.svg
@@ -0,0 +1,1290 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/mul_16384x16384_gmpz.svg b/tests/beman/big_int/perf/flamegraphs/mul_16384x16384_gmpz.svg
new file mode 100644
index 00000000..4631184e
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/mul_16384x16384_gmpz.svg
@@ -0,0 +1,2546 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/mul_262144x262144_auto.svg b/tests/beman/big_int/perf/flamegraphs/mul_262144x262144_auto.svg
new file mode 100644
index 00000000..e9b79444
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/mul_262144x262144_auto.svg
@@ -0,0 +1,1458 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/mul_262144x262144_gmpz.svg b/tests/beman/big_int/perf/flamegraphs/mul_262144x262144_gmpz.svg
new file mode 100644
index 00000000..52574461
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/mul_262144x262144_gmpz.svg
@@ -0,0 +1,2562 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/mul_64x64_auto.svg b/tests/beman/big_int/perf/flamegraphs/mul_64x64_auto.svg
new file mode 100644
index 00000000..5908af79
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/mul_64x64_auto.svg
@@ -0,0 +1,786 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/mul_64x64_gmpz.svg b/tests/beman/big_int/perf/flamegraphs/mul_64x64_gmpz.svg
new file mode 100644
index 00000000..7e5f7a85
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/mul_64x64_gmpz.svg
@@ -0,0 +1,546 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/tochars_10000x10_auto.svg b/tests/beman/big_int/perf/flamegraphs/tochars_10000x10_auto.svg
new file mode 100644
index 00000000..66be7f3e
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/tochars_10000x10_auto.svg
@@ -0,0 +1,2574 @@
+
+
+
diff --git a/tests/beman/big_int/perf/flamegraphs/tochars_10000x10_gmpz.svg b/tests/beman/big_int/perf/flamegraphs/tochars_10000x10_gmpz.svg
new file mode 100644
index 00000000..ff381b08
--- /dev/null
+++ b/tests/beman/big_int/perf/flamegraphs/tochars_10000x10_gmpz.svg
@@ -0,0 +1,2610 @@
+
+
+
diff --git a/tests/beman/big_int/perf/gmp_gap_analysis.md b/tests/beman/big_int/perf/gmp_gap_analysis.md
new file mode 100644
index 00000000..5b813b50
--- /dev/null
+++ b/tests/beman/big_int/perf/gmp_gap_analysis.md
@@ -0,0 +1,600 @@
+
+
+# GMP gap analysis: where `big_int` must get faster to match GMP
+
+Analysis only: no library code was changed. Method: `shape_sweep` (extended for this study, see `shape_sweep.cpp`)
+times, per op and shape, the public API into a reused destination (`auto`), compound assignment (`inplace`), the
+library's span kernels on preallocated buffers (`kernel`), GMP `mpn` on preallocated buffers (`gmp`) and GMP `mpz` with
+destination reuse (`gmpz`). **auto/gmpz** is the gap a user sees; **kernel/gmp** is the algorithm-and-codegen gap;
+**auto - kernel** (ns) is the front-end cost. A ratio above 1 means `big_int` is slower. All ratios are like-for-like
+medians from one process per shape; absolute cycle counts are harness numbers (see Caveats).
+
+## 1. Summary
+
+### 1.1 Heat table: auto/gmpz, geometric mean over the shapes of a band (worst shape in parentheses)
+
+Bands: small <= 16 limbs, medium <= 2000 (divrem: <= 4000), large above. x64 = i9-11900K, g++-14 `-march=native`
+(BMI2/ADX + AVX-512 IFMA kernels, SIMD_MUL), two seeds averaged; M4 = Apple M4 Max, AppleClang, SIMD_MUL, two seeds.
+
+| op | band | x64 gcc14 auto/gmpz (geomean, worst) | M4 auto/gmpz (geomean, worst) |
+|---|---|---|---|
+| add | small | 3.60 (4.42) | 3.21 (3.81) |
+| add | medium | 2.52 (3.80) | 2.04 (4.20) |
+| add | large | 1.90 (2.15) | 2.20 (2.26) |
+| sub | small | 3.51 (4.15) | 4.44 (5.66) |
+| sub | medium | 2.53 (3.85) | 1.91 (3.00) |
+| sub | large | 2.00 (2.19) | 2.20 (2.28) |
+| shl | small | 4.59 (4.91) | 6.55 (8.91) |
+| shl | medium | 2.90 (3.14) | 1.33 (2.70) |
+| shl | large | 3.00 (3.06) | 2.51 (2.61) |
+| shr | small | 3.84 (5.07) | 5.44 (6.80) |
+| shr | medium | 2.88 (3.19) | 2.61 (3.64) |
+| shr | large | 2.77 (2.94) | 2.62 (2.69) |
+| cmp | small | 0.58 (0.62) | 1.07 (1.33) |
+| cmp | medium | 0.74 (0.74) | 1.03 (1.05) |
+| cmp | large | 0.70 (0.70) | 1.02 (1.02) |
+| mul | small | 2.39 (5.01) | 2.50 (5.56) |
+| mul | medium | 0.80 (1.24) | 1.54 (2.02) |
+| mul | large | 2.33 (3.04) | 2.31 (3.44) |
+| sqr | small | 2.42 (3.83) | 2.03 (3.10) |
+| sqr | medium | 0.72 (0.99) | 1.39 (1.88) |
+| sqr | large | 2.96 (3.63) | 2.42 (3.76) |
+| divrem | small | 2.94 (3.46) | 2.73 (3.41) |
+| divrem | medium | 1.71 (2.00) | 1.82 (2.31) |
+| divrem | large | 2.44 (4.69) | 2.95 (5.52) |
+| tochars | small | 0.93 (1.97) | 0.63 (1.37) |
+| tochars | medium | 1.36 (1.90) | 1.23 (1.66) |
+| tochars | large | 1.58 (2.77) | 2.01 (3.58) |
+| fromchars | small | 2.15 (2.84) | 1.20 (1.20) |
+| fromchars | medium | 2.39 (3.01) | 1.37 (1.47) |
+| fromchars | large | 2.33 (2.80) | 2.97 (3.43) |
+| gcd | small | 1.90 (3.65) | 1.79 (3.32) |
+| gcd | medium | 2.06 (2.72) | 2.41 (3.90) |
+| gcd | large | 15.01 (24.56) | 20.04 (32.80) |
+
+Reading it: `big_int` already beats GMP on x64 for mul/sqr at 32-512 limbs (IFMA kernels) and for cmp everywhere, and
+is close for conversions below ~2000 limbs. It loses by 3-5x on small add/sub/shift (fixed per-call cost), 2-3x on
+medium and large linear ops, 2-3x on mul/sqr above ~16k limbs, 3-5x on large divrem, and by an unbounded factor on large
+gcd (quadratic algorithm).
+
+### 1.2 Direct answers
+
+**Do add/sub need assembly?** Not on x64, and not first anywhere. On x64 `add_n` in the library is 1.5x GMP in
+cycles/limb with g++-14 (1.6x clang-23, 2.3x g++-13, whose carry falls back to two `__builtin_add_overflow` and an `||`),
+but a 4x-unrolled `__builtin_addcll` loop in plain C++ reaches 1.12x GMP at 64 limbs (1.16-1.28x at 512 limbs) and an
+inline-asm `adc` x4 loop reaches 1.09x (1.02-1.03x at 512): asm buys ~4-7% over the unrolled builtins at 64 limbs and
+~12-21% at 512 limbs, which is closer to the span lengths of the Toom/Karatsuba linear passes in a 16k-limb mul. The same unroll closes ~75% of the gap on the M4 (3.9x -> 1.7x GMP). So the
+order is: restructure the C++ kernels (unrolled carry chain, one carry materialization per 4 limbs), re-measure at 512+
+limbs, and only then decide on asm: x64 `adc` asm for the remaining 12-21% and AArch64 `adcs`/`sbcs` asm for the
+remaining ~1.7x on M4. At 64-2000 limbs the plan's criterion is met
+(kernel/gmp 1.5-2.4x x64, 3.8-4.3x M4, and linear helpers are 24-96% of the profiled mul/div/conversion/gcd time), and the
+"unrolled builtins close the gap" clause says to do those first.
+
+**Are `span_ops` a slowdown?** Yes, wherever linear passes dominate, and the cause is codegen and structure, not the
+algorithm:
+- `kernel/gmp` for add/sub is 1.5-2.4x (x64) and 3.8-4.3x (M4) at 64-2000 limbs; shl/shr 2.0-2.5x on x64 with gcc (clang
+ vectorizes `shift_left_n` and beats GMP, 0.69x); `submul_single_limb` 1.5x (x64) / 3.1x (M4); `multiply_single_limb`
+ 2.2x / 1.9x; `divide_unsigned_short` by 3 is 5.2x GMP's `divexact_by3c`.
+- Inclusive `span_ops` time (including the carry primitives of `wide_ops` beneath it) is 46% of mul 16384x16384, 96% of
+ divrem 256x128, 51% of divrem 4096x2048, 54% of tochars/fromchars 10000 limbs and ~80% of gcd 1024.
+- If those helpers ran 1.5-2x faster, projected ratios move: mul 16384 2.14 -> 1.64-1.81, divrem 4096x2048 1.37 -> 1.02-1.14,
+ tochars 10000 1.39 -> 1.03-1.15, gcd 1024 2.20 -> 1.32-1.61, divrem 131072x65536 3.19 -> 2.4-2.7 (table in 4.2).
+ Large mul (FFT) and large divrem do not close by this alone.
+Where they are not the problem: mul 64x64 (asm kernel is 93% of the time and beats GMP by 1.7x), mul 262144 (FFT), cmp.
+
+### 1.3 Top-5 actions (full ranked list with evidence in section 6)
+
+1. **Cut the fixed per-call front-end cost** (S-M, both archs). `auto - kernel` is 13-18 ns for add/sub/shift and ~21 ns
+ for mul at <= 16 limbs: 3.6-4.6x GMP on add/sub/shl small shapes; 46% member/`construct_at`, 43% allocation at add 16x16.
+2. **Rewrite the linear span kernels in C++** as 4x unrolled carry chains (S-M, both archs; fixes the g++-13 fallback
+ and makes gcc shifts vectorize like clang). Closes ~75% of add/sub on M4, 1.5-1.7x -> 1.1-1.3x on x64 (64-512 limbs); worth 15-23% of mul 16384
+ and most of the divrem 4096x2048 / tochars 10000 / gcd 1024 gaps.
+3. **HGCD (or at least a subquadratic gcd) above ~2000 limbs** (L). Ratio grows 2.2 (1k) -> 9.2 (16k) -> 24.6 (65k) on
+ x64, 2.4 -> 12.2 -> 32.7 on M4; no linear-kernel work helps beyond 1k limbs.
+4. **Large-multiplication path: NTT speed and the Toom/FFT crossover** (L). mul/sqr/divrem above 16k limbs are 2-3.6x
+ (x64) with 65% `fft_ntt` + 26% `wide_ops` at 262144 limbs; large divrem/tochars/fromchars inherit it (88% of
+ divrem 131072x65536 is inside multiply).
+5. **Re-tune cutoffs after item 2 and use exact division in Toom** (M). Our asm basecase alone (1.98 ms) exceeds GMP's
+ whole mul 16384 (1.77 ms); the divexact-by-3/5 steps use a general short division 5x slower than GMP's.
+ Asm for AArch64 add/sub/mul_1/submul_1 is item 6, gated on item 2's result.
+
+## 2. Setup
+
+| | x64 | M4 |
+|---|---|---|
+| CPU | Intel i9-11900K (Rocket Lake, AVX-512 IFMA), pinned `taskset -c 2`, turbo 5.26-5.29 GHz measured | Apple M4 Max, P-core assumed 4.47 GHz (not pinned) |
+| Compilers | g++ 14.3.0 (primary), clang 23.0.0, g++ 13.4.0 | AppleClang 21.0.0 |
+| Flags | `-march=native`, effective **-O2** (see below), RelWithDebInfo, NDEBUG | effective -O2, RelWithDebInfo |
+| big_int config | BMI2/ADX=1, AVX512_IFMA=1, SIMD_MUL=1 (noIFMA run: IFMA=0) | SIMD_MUL=1, NEON |
+| GMP | 6.3.0 built in `~/tools/gmp` for rocketlake (coreibwl mulx/adx mul_basecase/addmul_1, coreihwl aors_n), -O2 | 6.3.0 Homebrew, `libgmp.a` |
+| Seeds / rounds | seed 1 and 2 (gcc14, M4); seed 1 (others); 9 rounds x 20 ms, large band 3 rounds, gcd 65536 1 round | same |
+
+- **-O2 finding:** the release presets look like -O3 (`-march=native -O3 -O2 -g -DNDEBUG` in the `#const cxx_flags`)
+ but CMake's `CMAKE_CXX_FLAGS_RELWITHDEBINFO` (`-O2 -g -DNDEBUG`) comes last, so all builds here (and users of the presets)
+ run at -O2. The probe shows -O3 leaves the linear-kernel c/l unchanged within 5% (add_n, shifts, submul_1, mul_1, all three compilers); the sweeps were not re-run at -O3.
+- **Code state:** `git rev-parse HEAD` = `22ef17b12246e1ebed3571208589e7785ec388ea` (branch `gmp-gap-analysis`). The only
+ change is the uncommitted extension of `tests/beman/big_int/perf/shape_sweep.cpp`.
+- **Tuning tables:** the IFMA build uses karatsuba 260, toom3 1600, toom4 4000, fft_mul_min 16000, Burnikel-Ziegler 160.
+ The IFMA=OFF build also switches to the BMI2 table (karatsuba 47, toom3 400, toom4 1600, fft 6000), so the noIFMA column
+ changes kernels **and** cutoffs. M4 uses karatsuba 112, toom3 300, toom4 1400, fft 3300, BZ 40.
+- **`#const` (abbreviated):** x64 gcc14 `ARCH=x86_64 BMI2_ADX=1 AVX512_IFMA=1 SIMD_MUL=1 limb_bits=64 ndebug=1
+ isa_avx512ifma=1 compiler=gcc-14.3.0 build_type=RelWithDebInfo`; M4 `ARCH=aarch64 SIMD_MUL=1 limb_bits=64
+ compiler=appleclang-21.0.0`. The full line is the third line of every CSV.
+- **Shape conventions:** add/sub/cmp/gcd `AxB` limbs (cmp operands equal except limb 0; gcd with b odd); shl/shr `NxS` =
+ N limbs by S bits (13, plus 77 for the whole-limb case); tochars/fromchars `NxBASE` (10; base 16 for tochars); divrem
+ `2N x N`, plus 2000x100 and 65536x1. Kernel shift rows include a copy-in (in-place kernels); gcd `kernel` is
+ `gcd_unsigned_spans` alone (no Euclidean pre-steps), valid for square shapes only.
+- **Data files** (this directory): `gmp_gap_x64_gcc14.csv` (seed 1), `gmp_gap_x64_clang23.csv`,
+ `gmp_gap_x64_gcc13_linear.csv` (add, sub, shl, shr, cmp), `gmp_gap_x64_gcc14_noifma.csv` (mul, sqr), `gmp_gap_m4.csv`
+ (seed 1). The tables below average both seeds where two exist. Seed-to-seed spread: 35 of 518 x64 rows and 51 of 518 M4
+ rows differ by more than 5% (mostly sub-20 ns rows and 1M-limb DRAM-bound rows; they also see different random operands).
+
+## 3. Ratio tables
+
+Columns: x64 g++-14 `auto/gmpz`, `kernel/gmp`, `inplace/gmpz` (per op, already halved), `auto - kernel` in ns; then
+clang-23 `auto/gmpz` and `kernel/gmp`; g++-13 `kernel/gmp` (linear ops only); noIFMA `auto/gmpz` (mul, sqr only); then M4
+`auto/gmpz`, `kernel/gmp`, `inplace/gmpz`, `auto - kernel` ns. "-" = row not measured or not applicable. mul/sqr/divrem
+`kernel` is the runtime tier ladder, so `auto - kernel` there is dispatcher overhead (negative values: the auto path
+slices/shapes better than the `unsliced` kernel row, or noise).
+
+#### add
+
+| shape | x64 auto/gmpz | x64 kern/gmp | x64 inpl/gmpz | x64 auto-kern ns | clang auto/gmpz | clang kern/gmp | gcc13 kern/gmp | noIFMA auto/gmpz | M4 auto/gmpz | M4 kern/gmp | M4 inpl/gmpz | M4 auto-kern ns |
+|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
+| 1x1 | 1.97 | 0.91 | 0.93 | 6 | 1.90 | 0.83 | 0.92 | - | 2.22 | 0.77 | 1.14 | 5 |
+| 2x2 | 4.42 | 1.08 | 1.28 | 16 | 3.97 | 1.05 | 1.12 | - | 3.47 | 0.72 | 0.94 | 13 |
+| 4x4 | 4.29 | 1.26 | 1.22 | 15 | 3.83 | 1.21 | 1.37 | - | 3.79 | 1.09 | 0.95 | 13 |
+| 8x8 | 4.17 | 1.24 | 1.52 | 15 | 3.76 | 1.22 | 1.51 | - | 3.05 | 1.57 | 1.02 | 12 |
+| 16x16 | 3.91 | 1.37 | 1.73 | 18 | 3.50 | 1.55 | 2.11 | - | 3.81 | 2.30 | 2.28 | 9 |
+| 64x64 | 3.80 | 2.18 | 2.38 | 29 | 2.40 | 2.59 | 3.11 | - | 4.20 | 4.25 | 3.90 | 7 |
+| 256x256 | 2.80 | 1.82 | 1.83 | 81 | 1.81 | 2.14 | 2.47 | - | 2.51 | 4.00 | 4.04 | -82 |
+| 1024x1024 | 2.14 | 1.48 | 1.49 | 261 | 1.28 | 1.73 | 1.99 | - | 2.17 | 3.79 | 3.83 | -394 |
+| 2000x2000 | 3.69 | 2.43 | 2.41 | 605 | 2.21 | 2.78 | 3.28 | - | 2.27 | 3.76 | 3.83 | -715 |
+| 1024x1 | 1.20 | 2.49 | 2.48 | -298 | 0.66 | 1.25 | 2.48 | - | 0.67 | 2.70 | 3.58 | -533 |
+| 16384x16384 | 1.90 | 1.23 | 1.19 | 5531 | 1.36 | 1.44 | 1.65 | - | 2.26 | 3.72 | 3.88 | -6002 |
+| 131072x131072 | 2.15 | 1.24 | 1.25 | 53610 | 1.41 | 1.46 | 1.69 | - | 2.19 | 3.73 | 3.78 | -49938 |
+| 1048576x1048576 | 1.68 | 1.20 | 0.93 | 338532 | 1.35 | 1.19 | 1.31 | - | 2.14 | 3.76 | 3.86 | -430539 |
+
+#### sub
+
+| shape | x64 auto/gmpz | x64 kern/gmp | x64 inpl/gmpz | x64 auto-kern ns | clang auto/gmpz | clang kern/gmp | gcc13 kern/gmp | noIFMA auto/gmpz | M4 auto/gmpz | M4 kern/gmp | M4 inpl/gmpz | M4 auto-kern ns |
+|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
+| 1x1 | 2.14 | 0.67 | 0.85 | 8 | 1.91 | 0.67 | 0.75 | - | 2.91 | 0.71 | 1.34 | 6 |
+| 2x2 | 4.15 | 0.92 | 1.01 | 16 | 3.68 | 0.97 | 1.00 | - | 4.98 | 0.74 | 1.08 | 14 |
+| 4x4 | 4.06 | 1.14 | 1.14 | 16 | 3.53 | 1.09 | 1.23 | - | 5.66 | 1.10 | 1.37 | 14 |
+| 8x8 | 4.05 | 1.18 | 1.38 | 16 | 3.49 | 1.04 | 1.58 | - | 4.91 | 1.58 | 1.57 | 13 |
+| 16x16 | 3.67 | 1.41 | 1.52 | 17 | 3.34 | 1.20 | 2.13 | - | 4.29 | 2.38 | 2.31 | 10 |
+| 64x64 | 3.85 | 2.18 | 2.26 | 31 | 2.32 | 1.57 | 3.10 | - | 3.00 | 4.39 | 3.68 | -7 |
+| 256x256 | 2.88 | 1.83 | 1.82 | 87 | 1.77 | 1.50 | 2.47 | - | 2.28 | 4.03 | 4.02 | -97 |
+| 1024x1024 | 2.17 | 1.48 | 1.49 | 270 | 1.27 | 1.23 | 1.99 | - | 2.16 | 3.77 | 3.83 | -400 |
+| 2000x2000 | 3.58 | 2.38 | 2.33 | 610 | 2.24 | 1.98 | 3.18 | - | 2.23 | 3.74 | 3.81 | -741 |
+| 1024x1 | 1.21 | 2.47 | 2.48 | -293 | 0.41 | 1.45 | 2.46 | - | 0.77 | 2.78 | 3.66 | -519 |
+| 16384x16384 | 2.16 | 1.26 | 1.34 | 5732 | 1.38 | 1.12 | 1.78 | - | 2.28 | 3.76 | 3.82 | -6215 |
+| 131072x131072 | 2.19 | 1.27 | 1.27 | 53529 | 1.40 | 1.04 | 1.68 | - | 2.19 | 3.78 | 3.89 | -51958 |
+| 1048576x1048576 | 1.69 | 1.10 | 0.92 | 417562 | 1.36 | 1.00 | 1.30 | - | 2.14 | 3.82 | 3.83 | -439473 |
+
+#### shl
+
+| shape | x64 auto/gmpz | x64 kern/gmp | x64 inpl/gmpz | x64 auto-kern ns | clang auto/gmpz | clang kern/gmp | gcc13 kern/gmp | noIFMA auto/gmpz | M4 auto/gmpz | M4 kern/gmp | M4 inpl/gmpz | M4 auto-kern ns |
+|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
+| 1x13 | 4.91 | 1.00 | 1.20 | 15 | 5.35 | 1.00 | 0.95 | - | 8.91 | 2.00 | 1.15 | 16 |
+| 4x13 | 4.66 | 1.37 | 1.30 | 15 | 5.82 | 1.52 | 1.52 | - | 7.58 | 1.88 | 1.32 | 17 |
+| 16x13 | 4.22 | 1.81 | 1.60 | 17 | 4.47 | 0.92 | 2.02 | - | 4.16 | 1.08 | 1.10 | 18 |
+| 256x13 | 2.87 | 1.98 | 1.85 | 51 | 1.48 | 0.53 | 2.31 | - | 2.30 | 1.32 | 0.97 | 39 |
+| 2000x13 | 2.71 | 2.14 | 1.94 | 200 | 1.44 | 0.46 | 2.05 | - | 0.38 | 0.27 | 0.17 | 246 |
+| 2000x77 | 3.14 | 2.53 | 2.26 | 213 | 1.82 | 0.86 | 2.43 | - | 2.70 | 1.91 | 1.46 | 232 |
+| 131072x13 | 2.95 | 2.08 | 1.29 | 29476 | 2.14 | 1.28 | 2.02 | - | 2.42 | 1.49 | 0.98 | 19425 |
+| 1048576x13 | 3.06 | 1.94 | 0.94 | 415797 | 2.71 | 1.69 | 1.91 | - | 2.61 | 1.50 | 1.05 | 173676 |
+
+#### shr
+
+| shape | x64 auto/gmpz | x64 kern/gmp | x64 inpl/gmpz | x64 auto-kern ns | clang auto/gmpz | clang kern/gmp | gcc13 kern/gmp | noIFMA auto/gmpz | M4 auto/gmpz | M4 kern/gmp | M4 inpl/gmpz | M4 auto-kern ns |
+|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
+| 1x13 | 3.21 | 0.90 | 1.14 | 9 | 2.93 | 0.91 | 0.90 | - | 4.74 | 2.52 | 1.12 | 7 |
+| 4x13 | 5.07 | 1.48 | 1.19 | 17 | 4.94 | 1.45 | 1.49 | - | 6.80 | 2.27 | 1.19 | 15 |
+| 16x13 | 3.49 | 2.02 | 1.22 | 13 | 4.66 | 4.17 | 2.75 | - | 4.99 | 1.28 | 1.19 | 17 |
+| 256x13 | 2.67 | 2.31 | 1.84 | 25 | 1.29 | 1.01 | 2.29 | - | 2.27 | 1.80 | 0.94 | 20 |
+| 2000x13 | 2.80 | 2.39 | 1.94 | 136 | 1.18 | 1.02 | 2.42 | - | 2.16 | 1.66 | 0.94 | 142 |
+| 2000x77 | 3.19 | 2.46 | 2.26 | 255 | 1.13 | 1.08 | 2.46 | - | 3.64 | 1.77 | 1.45 | 536 |
+| 131072x13 | 2.61 | 1.96 | 1.15 | 22856 | 1.92 | 1.23 | 1.99 | - | 2.55 | 1.91 | 1.00 | 12337 |
+| 1048576x13 | 2.94 | 2.11 | 0.92 | 322697 | 2.62 | 1.72 | 2.09 | - | 2.69 | 1.94 | 1.06 | 110119 |
+
+#### cmp
+
+| shape | x64 auto/gmpz | x64 kern/gmp | x64 inpl/gmpz | x64 auto-kern ns | clang auto/gmpz | clang kern/gmp | gcc13 kern/gmp | noIFMA auto/gmpz | M4 auto/gmpz | M4 kern/gmp | M4 inpl/gmpz | M4 auto-kern ns |
+|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
+| 1x1 | 0.61 | 0.71 | - | 0 | 0.85 | 0.73 | 0.74 | - | 1.33 | 1.19 | - | 0 |
+| 4x4 | 0.50 | 0.58 | - | 0 | 0.62 | 0.57 | 0.51 | - | 0.92 | 1.09 | - | 1 |
+| 16x16 | 0.62 | 1.06 | - | -1 | 0.66 | 1.36 | 1.35 | - | 0.99 | 1.02 | - | 0 |
+| 256x256 | 0.73 | 1.02 | - | 4 | 1.05 | 1.52 | 1.02 | - | 1.02 | 1.10 | - | 1 |
+| 2000x2000 | 0.74 | 0.99 | - | 60 | 1.01 | 1.56 | 0.99 | - | 1.05 | 1.21 | - | 20 |
+| 131072x131072 | 0.70 | 1.06 | - | -3092 | 0.79 | 1.10 | 0.99 | - | 1.02 | 1.18 | - | -6049 |
+
+#### mul
+
+| shape | x64 auto/gmpz | x64 kern/gmp | x64 inpl/gmpz | x64 auto-kern ns | clang auto/gmpz | clang kern/gmp | gcc13 kern/gmp | noIFMA auto/gmpz | M4 auto/gmpz | M4 kern/gmp | M4 inpl/gmpz | M4 auto-kern ns |
+|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
+| 1x1 | 5.01 | - | - | - | 3.44 | - | - | 4.76 | 5.56 | - | - | - |
+| 2x2 | 4.68 | 1.96 | - | 21 | 4.22 | 1.71 | - | 4.57 | 4.29 | 1.40 | - | 22 |
+| 3x3 | 3.03 | 1.13 | - | 21 | 2.77 | 1.01 | - | 2.97 | 2.79 | 1.32 | - | 21 |
+| 4x4 | 2.65 | 1.14 | - | 21 | 2.47 | 0.96 | - | 2.65 | 2.86 | 1.22 | - | 18 |
+| 6x6 | 2.13 | 1.05 | - | 21 | 2.34 | 1.00 | - | 2.16 | 1.84 | 1.26 | - | 14 |
+| 8x8 | 1.59 | 0.93 | - | 21 | 1.52 | 0.85 | - | 1.58 | 1.68 | 0.93 | - | 18 |
+| 12x12 | 1.39 | 0.97 | - | 23 | 1.33 | 0.93 | - | 1.31 | 1.34 | 0.95 | - | 20 |
+| 16x16 | 1.20 | 0.96 | - | 22 | 1.19 | 0.93 | - | 1.20 | 1.95 | 0.92 | - | 84 |
+| 32x32 | 0.80 | 0.73 | - | 21 | 0.77 | 0.68 | - | 1.26 | 1.14 | 0.98 | - | 42 |
+| 64x64 | 0.60 | 0.56 | - | 38 | 0.59 | 0.56 | - | 1.39 | 1.32 | 1.27 | - | 45 |
+| 128x128 | 0.65 | 0.63 | - | 45 | 0.64 | 0.60 | - | 1.44 | 1.32 | 1.29 | - | 68 |
+| 256x256 | 0.81 | 0.80 | - | 64 | 0.80 | 0.80 | - | 1.60 | 1.50 | 1.51 | - | -104 |
+| 512x512 | 0.93 | 0.92 | - | 199 | 0.91 | 0.91 | - | 1.83 | 1.95 | 1.94 | - | 295 |
+| 1000x1000 | 1.13 | 1.13 | - | -22 | 1.11 | 1.10 | - | 2.06 | 1.96 | 1.93 | - | 447 |
+| 2000x2000 | 1.24 | 1.23 | - | 899 | 1.21 | 1.20 | - | 2.05 | 2.02 | 2.03 | - | -733 |
+| 2000x100 | 0.50 | 0.49 | - | 188 | 0.50 | 0.50 | - | 1.43 | 1.35 | 1.30 | - | 777 |
+| 4096x4096 | 1.34 | 1.32 | - | 6352 | 1.34 | 1.33 | - | 2.21 | 2.64 | 2.58 | - | 3341 |
+| 16384x16384 | 2.13 | 2.11 | - | 3150 | 2.13 | 2.13 | - | 4.29 | 3.44 | 3.36 | - | -17370 |
+| 65536x65536 | 2.85 | 2.78 | - | -56190 | 2.82 | 2.81 | - | 3.71 | 2.51 | 2.51 | - | -36334 |
+| 262144x262144 | 2.79 | 2.72 | - | 2716664 | 2.13 | 2.09 | - | 2.79 | 1.94 | 1.69 | - | 926500 |
+| 1048576x1048576 | 3.04 | 3.03 | - | 1543947 | 2.53 | 2.55 | - | 3.04 | 1.29 | 1.32 | - | 457750 |
+| 262144x8192 | 2.32 | 3.06 | - | -15235319 | 2.25 | 3.03 | - | 3.45 | 2.68 | 2.71 | - | -881146 |
+
+#### sqr
+
+| shape | x64 auto/gmpz | x64 kern/gmp | x64 inpl/gmpz | x64 auto-kern ns | clang auto/gmpz | clang kern/gmp | gcc13 kern/gmp | noIFMA auto/gmpz | M4 auto/gmpz | M4 kern/gmp | M4 inpl/gmpz | M4 auto-kern ns |
+|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
+| 4x4 | 3.83 | 1.66 | - | 24 | 3.48 | 1.50 | - | 3.73 | 3.10 | 1.56 | - | 19 |
+| 16x16 | 1.53 | 1.12 | - | 23 | 1.48 | 1.23 | - | 1.65 | 1.33 | 0.96 | - | 27 |
+| 64x64 | 0.54 | 0.50 | - | 22 | 0.53 | 0.50 | - | 1.31 | 0.95 | 0.86 | - | 49 |
+| 512x512 | 0.70 | 0.70 | - | 112 | 0.69 | 0.69 | - | 1.72 | 1.50 | 1.34 | - | 3 |
+| 2000x2000 | 0.99 | 0.98 | - | 1396 | 0.99 | 0.98 | - | 2.06 | 1.88 | 1.85 | - | 3488 |
+| 16384x16384 | 2.11 | 2.09 | - | 24542 | 2.09 | 2.07 | - | 6.25 | 3.76 | 3.78 | - | 7675 |
+| 262144x262144 | 3.40 | 3.40 | - | 117972 | 3.39 | 3.38 | - | 3.77 | 2.42 | 2.43 | - | 706730 |
+| 1048576x1048576 | 3.63 | 3.62 | - | 1857343 | 2.83 | 2.84 | - | 3.62 | 1.56 | 1.55 | - | -828896 |
+
+#### divrem
+
+| shape | x64 auto/gmpz | x64 kern/gmp | x64 inpl/gmpz | x64 auto-kern ns | clang auto/gmpz | clang kern/gmp | gcc13 kern/gmp | noIFMA auto/gmpz | M4 auto/gmpz | M4 kern/gmp | M4 inpl/gmpz | M4 auto-kern ns |
+|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
+| 2x1 | 3.46 | 0.76 | - | 37 | 2.34 | 0.66 | - | - | 3.41 | 1.17 | - | 24 |
+| 8x4 | 2.50 | 1.05 | - | 66 | 2.39 | 1.20 | - | - | 2.19 | 1.13 | - | 49 |
+| 32x16 | 1.90 | 1.51 | - | 80 | 1.71 | 1.48 | - | - | 2.31 | 1.90 | - | 88 |
+| 128x64 | 1.94 | 1.86 | - | 147 | 1.89 | 1.84 | - | - | 2.11 | 1.98 | - | 194 |
+| 512x256 | 1.80 | 1.77 | - | 367 | 1.76 | 1.75 | - | - | 1.73 | 1.70 | - | 112 |
+| 2000x1000 | 1.33 | 1.32 | - | 1362 | 1.27 | 1.27 | - | - | 1.64 | 1.71 | - | 2808 |
+| 4000x2000 | 1.41 | 1.40 | - | 2352 | 1.39 | 1.38 | - | - | 1.93 | 2.00 | - | 3428 |
+| 2000x100 | 2.00 | 1.96 | - | 2749 | 1.98 | 1.96 | - | - | 1.39 | 1.38 | - | 671 |
+| 65536x1 | 1.32 | 1.13 | - | 27989 | 1.57 | 1.48 | - | - | 1.42 | 1.34 | - | 14130 |
+| 16384x8192 | 1.82 | 1.81 | - | 8877 | 1.82 | 1.79 | - | - | 3.03 | 3.20 | - | -55484 |
+| 131072x65536 | 3.19 | 3.19 | - | -37358 | 3.17 | 3.15 | - | - | 5.52 | 5.54 | - | -1140708 |
+| 1048576x524288 | 4.69 | 4.70 | - | -3279742 | 3.57 | 3.56 | - | - | 3.17 | 3.01 | - | 3286834 |
+
+#### tochars
+
+| shape | x64 auto/gmpz | x64 kern/gmp | x64 inpl/gmpz | x64 auto-kern ns | clang auto/gmpz | clang kern/gmp | gcc13 kern/gmp | noIFMA auto/gmpz | M4 auto/gmpz | M4 kern/gmp | M4 inpl/gmpz | M4 auto-kern ns |
+|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
+| 1x10 | 0.43 | 2.97 | - | -48 | 0.42 | 2.76 | - | - | 0.25 | 1.99 | - | -27 |
+| 4x10 | 1.35 | 2.53 | - | -51 | 1.23 | 2.37 | - | - | 0.86 | 1.36 | - | -18 |
+| 16x10 | 1.97 | 2.05 | - | 127 | 2.08 | 2.13 | - | - | 1.37 | 1.45 | - | 97 |
+| 64x10 | 1.90 | 1.98 | - | 368 | 1.89 | 1.96 | - | - | 1.49 | 1.56 | - | 307 |
+| 256x10 | 1.83 | 1.87 | - | 1291 | 1.79 | 1.83 | - | - | 1.66 | 1.66 | - | 1461 |
+| 2000x10 | 1.49 | 1.46 | - | 22512 | 1.46 | 1.44 | - | - | 1.62 | 1.62 | - | 4983 |
+| 16384x10 | 1.52 | 1.52 | - | 87912 | 1.48 | 1.49 | - | - | 2.35 | 2.36 | - | 134176 |
+| 131072x10 | 2.20 | 2.22 | - | 750279 | 2.15 | 2.18 | - | - | 3.41 | 3.42 | - | 1418730 |
+| 262144x10 | 2.77 | 2.77 | - | 3817818 | 2.56 | 2.56 | - | - | 3.58 | 3.61 | - | 1700062 |
+| 16x16 | 0.66 | - | - | - | 0.70 | - | - | - | 0.53 | - | - | - |
+| 2000x16 | 0.67 | - | - | - | 0.79 | - | - | - | 0.57 | - | - | - |
+| 131072x16 | 0.66 | - | - | - | 1.03 | - | - | - | 0.57 | - | - | - |
+
+#### fromchars
+
+| shape | x64 auto/gmpz | x64 kern/gmp | x64 inpl/gmpz | x64 auto-kern ns | clang auto/gmpz | clang kern/gmp | gcc13 kern/gmp | noIFMA auto/gmpz | M4 auto/gmpz | M4 kern/gmp | M4 inpl/gmpz | M4 auto-kern ns |
+|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
+| 4x10 | 2.84 | 1.29 | - | 152 | 2.62 | 1.07 | - | - | 1.20 | 1.05 | - | 59 |
+| 16x10 | 1.63 | 1.74 | - | 201 | 1.38 | 1.17 | - | - | 1.19 | 1.33 | - | 188 |
+| 64x10 | 2.13 | 2.80 | - | 454 | 1.31 | 1.25 | - | - | 1.35 | 1.61 | - | 626 |
+| 256x10 | 3.01 | 3.51 | - | 1839 | 1.60 | 1.66 | - | - | 1.30 | 1.40 | - | 2243 |
+| 2000x10 | 2.14 | 2.20 | - | 20787 | 1.53 | 1.54 | - | - | 1.47 | 1.54 | - | 17033 |
+| 16384x10 | 1.94 | 1.96 | - | 145390 | 1.68 | 1.70 | - | - | 2.57 | 2.68 | - | 134445 |
+| 131072x10 | 2.80 | 2.82 | - | 1599741 | 2.49 | 2.55 | - | - | 3.43 | 3.42 | - | 3604896 |
+
+#### gcd
+
+| shape | x64 auto/gmpz | x64 kern/gmp | x64 inpl/gmpz | x64 auto-kern ns | clang auto/gmpz | clang kern/gmp | gcc13 kern/gmp | noIFMA auto/gmpz | M4 auto/gmpz | M4 kern/gmp | M4 inpl/gmpz | M4 auto-kern ns |
+|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
+| 2x2 | 3.65 | 3.50 | - | 48 | 3.01 | 2.96 | - | - | 3.32 | 3.21 | - | 21 |
+| 4x4 | 1.38 | 1.25 | - | 84 | 1.41 | 1.30 | - | - | 1.32 | 1.21 | - | 58 |
+| 16x16 | 1.36 | 1.32 | - | 114 | 1.22 | 1.19 | - | - | 1.31 | 1.29 | - | 84 |
+| 64x64 | 1.44 | 1.44 | - | 59 | 1.40 | 1.40 | - | - | 1.46 | 1.44 | - | 551 |
+| 256x256 | 2.09 | 1.92 | - | 2164 | 1.87 | 1.79 | - | - | 1.99 | 1.99 | - | 1726 |
+| 1024x1024 | 2.20 | 2.25 | - | -20099 | 1.85 | 1.93 | - | - | 3.00 | 3.12 | - | -25244 |
+| 2000x2000 | 2.72 | 2.73 | - | -8454 | 2.24 | 2.24 | - | - | 3.90 | 3.80 | - | 99146 |
+| 16384x16384 | 9.18 | 9.21 | - | -1089222 | 7.55 | 7.55 | - | - | 12.24 | 12.21 | - | -580396 |
+| 65536x65536 | 24.56 | 24.53 | - | 621130 | 20.20 | 20.05 | - | - | 32.80 | 32.58 | - | 1138792 |
+
+### 3.1 What the tables show
+
+- **Front end.** `auto - kernel` is 13-18 ns (x64) / 5-14 ns (M4) for add/sub/shift at <= 16 limbs and ~21 ns for mul/sqr
+ (x64); it is up to ~45% of the call at 64 limbs for add/sub (5-10% for mul) and vanishes above ~1000 limbs except on x64 add/sub (261 ns at 1024, 606 ns at
+ 2000; see 4.3) and shifts (200 ns at 2000).
+- **Compiler dependence of the public add path.** x64 `c = a + b` at 1024 limbs is 2.14x gmpz with g++-14 but 1.28x with
+ clang-23, and the g++ number is slower than the g++ span kernel (4.3 vs 3.0 c/l); on the M4 `auto` is 2.2x vs kernel/inplace
+ 3.8x because the 4-way unrolled `add_into` loop beats `add_unsigned_spans`/`add_in_place` there.
+- **1024x1 add.** The span kernel (`add_unsigned_spans`) walks all 1024 limbs for the carry tail and is 2.5x GMP; the
+ public path stops propagating when the carry dies (1.20x).
+- **Mul/sqr.** x64 wins at 32-512 limbs (0.56-0.93x; without IFMA the same shapes are 1.26-1.83x), crosses at ~700 limbs
+ and loses 1.24x (2000), 2.13x (16384), 2.8x (65536), 3.0x (1M). M4 never wins (1.1-2.0x at 16-2000 limbs) and is 3.4x at
+ 16384; its 1M-limb ratio is 1.29 (GMP is relatively slower there; cause not investigated).
+- **Div.** 1-2x at 32-2000 limbs (schoolbook below the 160-limb Burnikel-Ziegler cutoff on x64), 3.2x at 131072x65536 and
+ 4.7x at 1048576x524288 on x64. `kernel/gmp` is ~auto/gmpz everywhere, so there is no front-end gap.
+- **Conversions.** Tiny sizes and base 16 are faster than GMP on x64 (tochars 1x10 0.43x, 16x16 0.66x); 64-2000 limbs
+ 1.5-2.0x tochars, 2.1-3.0x fromchars; 262144 limbs 2.8x. fromchars is the worse direction. clang/gcc agree within noise.
+- **cmp.** x64 is faster than GMP (0.5-0.74x auto/gmpz, ~1.0x kernel/gmp). M4 is at parity (1.0-1.3x).
+- **Anomaly (M4 shl/shr):** GMP's `lshift` for 13 bits at 1000-4000 limbs ran 0.7-2 ns/limb, 4-10x its own speed at 512
+ or with a one-limb offset (2000x77); `gmpz` shows the same. Treat the M4 shl/shr medium ratios (and `shl 2000x13`) as
+ unreliable; the `2000x77` and 256-limb rows are clean.
+
+## 4. Profiles
+
+Recorded on x64 with the g++-14 binary used for the sweep: `perf record -e cycles:u -F 2000 --call-graph lbr`, then
+`perf script --inline`, stack-collapse, and a classifier that assigns each stack to the frame nearest the leaf. Buckets are
+heuristic (name based). `span_ops incl.` counts any stack with a `span_ops` frame, including the `wide_ops` carry/mul
+primitives beneath it. Flame graphs: [`flamegraphs/`](flamegraphs/) (`__{auto,gmpz}.svg`; add 1000 also
+`_inplace`).
+
+### 4.1 Where the time goes
+
+| profile (x64 gcc14) | auto | gmpz | ours top buckets | GMP top buckets | span_ops incl. |
+|---|---:|---:|---|---|---:|
+| add 16x16 | 26 ns | 7 ns | member_inline 46%, alloc_mem 43%, wide_ops 11% | linear 94%, alloc_mem 6% | 0% |
+| divrem 131072x65536 | 77.40 ms | 24.26 ms | asm_kernel 49%, span_ops 36%, wide_ops 14%, alloc_mem 1% | basecase 42%, linear 38%, fft 12%, toom 5% | 50% |
+| divrem 256x128 | 12.6 us | 5.5 us | wide_ops 55%, span_ops 42%, member_inline 1% | basecase 76%, division 12%, linear 10%, toom 3% | 96% |
+| divrem 4096x2048 | 511.4 us | 373.6 us | asm_kernel 47%, span_ops 27%, wide_ops 25% | basecase 46%, linear 32%, fft 13%, toom 6% | 51% |
+| fromchars 10000x10 | 3.76 ms | 2.03 ms | asm_kernel 38%, span_ops 38%, wide_ops 16%, base_conv 7% | basecase 53%, linear 24%, toom 12%, conv 9% | 54% |
+| gcd 1024x1024 | 1.95 ms | 889.3 us | span_ops 45%, wide_ops 35%, gcd 20% | basecase 57%, gcd 30%, linear 9%, toom 3% | 100% |
+| mul 1024x1024 | 62.4 us | 56.0 us | asm_kernel 74%, span_ops 21%, wide_ops 4% | basecase 62%, linear 24%, toom 12%, division 1% | 24% |
+| mul 16384x16384 | 3.77 ms | 1.77 ms | asm_kernel 52%, span_ops 35%, wide_ops 11%, alloc_mem 1% | linear 41%, basecase 37%, fft 19%, toom 2% | 46% |
+| mul 262144x262144 | 138.89 ms | 49.72 ms | fft_ntt 65%, wide_ops 26%, member_inline 5%, span_ops 3% | linear 46%, basecase 32%, fft 18%, toom 3% | 9% |
+| mul 64x64 | 522 ns | 861 ns | asm_kernel 93%, alloc_mem 4%, toom_karatsuba 2% | basecase 82%, linear 13%, toom 5% | 1% |
+| tochars 10000x10 | 6.01 ms | 4.31 ms | asm_kernel 36%, span_ops 28%, wide_ops 25%, base_conv 8% | basecase 54%, linear 22%, division 8%, toom 8% | 53% |
+
+Flame graphs: [add 16x16](flamegraphs/add_16x16_auto.svg) / [gmpz](flamegraphs/add_16x16_gmpz.svg),
+[add 1000](flamegraphs/add_1000x1000_auto.svg) / [inplace](flamegraphs/add_1000x1000_inplace.svg) / [gmpz](flamegraphs/add_1000x1000_gmpz.svg),
+[mul 64](flamegraphs/mul_64x64_auto.svg), [mul 1024](flamegraphs/mul_1024x1024_auto.svg),
+[mul 16384](flamegraphs/mul_16384x16384_auto.svg) / [gmpz](flamegraphs/mul_16384x16384_gmpz.svg),
+[mul 262144](flamegraphs/mul_262144x262144_auto.svg) / [gmpz](flamegraphs/mul_262144x262144_gmpz.svg),
+[divrem 256x128](flamegraphs/divrem_256x128_auto.svg), [4096x2048](flamegraphs/divrem_4096x2048_auto.svg),
+[131072x65536](flamegraphs/divrem_131072x65536_auto.svg),
+[tochars 10000](flamegraphs/tochars_10000x10_auto.svg), [fromchars 10000](flamegraphs/fromchars_10000x10_auto.svg),
+[gcd 1024](flamegraphs/gcd_1024x1024_auto.svg) (each has a `_gmpz.svg` sibling).
+
+### 4.2 Time saved if the linear helpers ran at GMP-like speed
+
+`span_ops incl.` ns x (1 - 1/r), with r = 1.5 (measured add/sub/submul_1 ratio at 64 limbs on x64) to r = 2.0
+(mul_1, shifts, M4-like). An estimate, not a measurement; it assumes every helper in the bucket speeds up by r.
+
+| profile | span_ops incl. ns | saved at r=1.5 | saved at r=2.0 | auto -> projected | gmpz | projected ratio |
+|---|---:|---:|---:|---:|---:|---:|
+| divrem 131072x65536 | 38.62 ms | 12.87 ms | 19.31 ms | 64.52 ms to 58.09 ms | 24.26 ms | 2.66 to 2.39 (now 3.19) |
+| divrem 256x128 | 12.1 us | 4.0 us | 6.1 us | 8.6 us to 6.6 us | 5.5 us | 1.57 to 1.20 (now 2.31) |
+| divrem 4096x2048 | 261.8 us | 87.3 us | 130.9 us | 424.1 us to 380.5 us | 373.6 us | 1.14 to 1.02 (now 1.37) |
+| fromchars 10000x10 | 2.04 ms | 680.2 us | 1.02 ms | 3.08 ms to 2.74 ms | 2.03 ms | 1.52 to 1.35 (now 1.85) |
+| gcd 1024x1024 | 1.57 ms | 522.0 us | 783.0 us | 1.43 ms to 1.17 ms | 889.3 us | 1.61 to 1.32 (now 2.20) |
+| mul 1024x1024 | 15.3 us | 5.1 us | 7.6 us | 57.3 us to 54.7 us | 56.0 us | 1.02 to 0.98 (now 1.11) |
+| mul 16384x16384 | 1.75 ms | 583.6 us | 875.4 us | 3.19 ms to 2.90 ms | 1.77 ms | 1.81 to 1.64 (now 2.14) |
+| mul 262144x262144 | 12.64 ms | 4.21 ms | 6.32 ms | 134.68 ms to 132.57 ms | 49.72 ms | 2.71 to 2.67 (now 2.79) |
+| tochars 10000x10 | 3.17 ms | 1.06 ms | 1.58 ms | 4.95 ms to 4.43 ms | 4.31 ms | 1.15 to 1.03 (now 1.39) |
+
+Reading: linear speed alone brings divrem 4096x2048, tochars 10000 and mul 1024 to parity and gcd 1024 from 2.2x to
+1.3-1.6x; it does not fix divrem 256x128 (1.2-1.6x left: the schoolbook division below the BZ cutoff 160 does 91% of its
+time in `submul_single_limb`, while GMP's profile is 76% `mul_basecase`-class, i.e. it is already
+divide-and-conquer there), nor mul 16384 (1.64-1.81x left), nor anything FFT-bound.
+
+### 4.3 Specific findings
+
+- **add 1000 `auto` on x64 (g++-14):** 33% `construct_at`, 28% `basic_big_int.hpp` (the `add_into` loop), 26% `wide_ops`
+ (`carrying_add`), 11% memmove from the copy-assign in `operator=` (the prvalue result appears to be copied into the
+ destination instead of stolen), 1.5% malloc. The store lambda writes through `std::construct_at` per limb and the
+ carry is re-materialized per limb. The kernel is 578 ns, `auto` 840 ns, gmpz 390 ns.
+- **add 16x16:** 43% allocation (`alloc_mem`), 46% member-function inline code; GMP spends 94% in the add loop.
+- **mul 16384:** our asm basecase alone is 1.98 ms (52%), more than GMP's whole multiply (1.77 ms, of which basecase 37%,
+ 0.66 ms; GMP's `linear` bucket is 41%, 0.73 ms). `span_ops` incl. is 1.75 ms (46%) versus GMP's ~0.73 ms linear work.
+- **mul 262144:** 65% `fft_ntt` + 26% `wide_ops` (modular multiply primitives); `span_ops` is only 3-9%. This is
+ an NTT-implementation/algorithm gap, not a linear-kernel gap.
+- **divrem 131072x65536:** 88% of the stacks are inside `multiply*` (Burnikel-Ziegler divide-and-conquer); the gap is the
+ large-mul gap.
+- **tochars/fromchars 10000:** the power-chain construction (`build_power_table`/`append_squared_power`) is only 4.5%
+ (tochars) and 7.1% (fromchars); tochars is 72% inside Burnikel-Ziegler division (39% in multiply) and fromchars 47% in
+ `combine_level` multiplies and 42% in the basecase. So at 10000 limbs rebuilding the power chain is not the lever; the
+ multiply/divide gap is.
+- **gcd 1024:** `span_ops` 45% + `wide_ops` 35% + the Lehmer driver 20%; GMP is 57% basecase + 30% `gcd` + 9% linear.
+
+## 5. Probe: linear kernel cycles/limb
+
+A standalone probe (`tools/probe/probe.cpp`; `noinline` wrappers, L1-resident, 64 limbs, -O2)
+times each library kernel against GMP's. x64 values are `perf stat` cycles per limb, M4 values are ns/limb x 4.47 GHz
+(approximate). "x GMP" = ratio to GMP's kernel on the same machine.
+
+| kernel | variant | gcc13 c/l | gcc14 c/l | clang23 c/l | M4 c/l | gcc14 / GMP | M4 / GMP |
+|---|---|---:|---:|---:|---:|---:|---:|
+| add_n | lib_add_unsigned_spans | 3.97 | 2.63 | 2.78 | 3.26 | 1.51 | 3.93 |
+| add_n | loop_carrying_add | 3.97 | 2.59 | 2.10 | 3.27 | 1.48 | 3.94 |
+| add_n | addcll_x4 | - | 1.97 | 1.91 | 1.43 | 1.12 | 1.73 |
+| add_n | asm_adc_x4 | 1.89 | 1.90 | 1.79 | - | 1.09 | - |
+| add_n | gmp | 1.75 | 1.75 | 1.71 | 0.83 | 1.00 | 1.00 |
+| sub_n | lib_subtract_unsigned_spans | 3.96 | 2.67 | 2.46 | 3.48 | 1.51 | 4.24 |
+| sub_n | loop_borrowing_sub | 3.96 | 2.65 | 2.28 | 3.30 | 1.50 | 4.02 |
+| sub_n | gmp | 1.75 | 1.77 | 1.73 | 0.82 | 1.00 | 1.00 |
+| lshift13 | lib_copy_shift_left_n | 2.72 | 2.55 | 0.72 | 1.06 | 2.39 | 1.19 |
+| lshift13 | gmp_copy_inplace | 1.41 | 0.78 | 1.49 | 1.53 | 0.73 | 1.72 |
+| lshift13 | gmp | 1.09 | 1.07 | 1.04 | 0.89 | 1.00 | 1.00 |
+| lshift1 | lib_copy_shift_left_n | 2.71 | 2.65 | 0.73 | 1.05 | 2.48 | 1.17 |
+| lshift1 | gmp_copy_inplace | 1.53 | 1.42 | 1.26 | 1.57 | 1.33 | 1.75 |
+| lshift1 | gmp | 1.07 | 1.07 | 1.05 | 0.90 | 1.00 | 1.00 |
+| lshift63 | lib_copy_shift_left_n | 2.87 | 2.75 | 0.74 | 1.06 | 2.58 | 1.18 |
+| lshift63 | gmp_copy_inplace | 1.30 | 1.40 | 1.39 | 1.57 | 1.32 | 1.75 |
+| lshift63 | gmp | 1.06 | 1.06 | 1.05 | 0.90 | 1.00 | 1.00 |
+| submul_1 | lib_submul_single_limb | 4.45 | 3.70 | 4.07 | 4.83 | 1.51 | 3.14 |
+| submul_1 | gmp | 2.53 | 2.46 | 2.60 | 1.54 | 1.00 | 1.00 |
+| mul_1 | lib_multiply_single_limb | 3.30 | 2.90 | 2.31 | 1.80 | 2.16 | 1.85 |
+| mul_1 | gmp | 1.36 | 1.34 | 1.26 | 0.98 | 1.00 | 1.00 |
+| divrem_1_norm | lib_divide_unsigned_short | 15.79 | 13.29 | 16.93 | 14.94 | 1.09 | 1.48 |
+| divrem_1_norm | gmp | 12.18 | 12.17 | 12.09 | 10.06 | 1.00 | 1.00 |
+| divrem_1_unnorm | lib_divide_unsigned_short | 15.75 | 14.29 | 17.11 | 14.91 | 1.13 | 1.45 |
+| divrem_1_unnorm | gmp | 12.55 | 12.61 | 12.51 | 10.30 | 1.00 | 1.00 |
+| divexact_3 | lib_divide_unsigned_short | 15.76 | 14.29 | 17.11 | 14.89 | - | - |
+| divexact_3 | gmp_divrem_1 | 12.55 | 12.61 | 12.50 | 10.23 | - | - |
+| divexact_3 | gmp_divexact_by3c | 2.73 | 2.74 | 2.76 | 1.71 | - | - |
+| divexact_3 | gmp_bdiv_q_1 | 7.63 | 7.65 | 7.61 | 4.47 | - | - |
+| divexact_3 | gmp_divexact_1 | 7.91 | 7.88 | 7.81 | 5.59 | - | - |
+
+### 5.0 Same kernels at 512 limbs (-O2)
+
+The 512-limb size is closer to the span lengths of the Toom/Karatsuba linear passes in large products.
+
+| kernel | variant | gcc13 c/l | gcc14 c/l | clang23 c/l | M4 c/l | gcc14 / GMP | M4 / GMP |
+|---|---|---:|---:|---:|---:|---:|---:|
+| add_n | lib_add_unsigned_spans | 4.00 | 2.96 | 2.97 | 3.91 | 1.71 | 3.71 |
+| add_n | loop_carrying_add | 4.00 | 2.95 | 2.47 | 3.99 | 1.70 | 3.79 |
+| add_n | addcll_x4 | - | 2.01 | 2.24 | 1.82 | 1.16 | 1.73 |
+| add_n | asm_adc_x4 | 1.78 | 1.78 | 1.76 | - | 1.02 | - |
+| add_n | gmp | 1.74 | 1.73 | 1.74 | 1.05 | 1.00 | 1.00 |
+| sub_n | lib_subtract_unsigned_spans | 3.99 | 3.02 | 2.45 | 3.96 | 1.73 | 3.75 |
+| sub_n | loop_borrowing_sub | 4.00 | 2.96 | 2.47 | 3.96 | 1.70 | 3.75 |
+| sub_n | gmp | 1.74 | 1.74 | 1.73 | 1.06 | 1.00 | 1.00 |
+| submul_1 | lib_submul_single_limb | 4.52 | 3.94 | 4.26 | 5.24 | 1.46 | 3.11 |
+| submul_1 | gmp | 2.71 | 2.71 | 2.71 | 1.69 | 1.00 | 1.00 |
+| mul_1 | lib_multiply_single_limb | 2.81 | 2.53 | 2.67 | 2.17 | 2.04 | 2.01 |
+| mul_1 | gmp | 1.20 | 1.24 | 1.20 | 1.08 | 1.00 | 1.00 |
+
+- Variants: `loop_carrying_add` is the library's `carrying_add` loop; `addcll_x4` a 4x-unrolled `__builtin_addcll` loop
+ (not available on g++-13); `asm_adc_x4` a 4x `adc` inline-asm loop (x64 only).
+- At 512 limbs `addcll_x4` is 1.16 (g++-14) / 1.29 (clang-23) x GMP while `asm_adc_x4` is 1.02-1.03x, so the asm advantage grows
+ from 4-7% (64 limbs) to 12-21% (512); a rewritten C++ kernel must be re-measured at 512+ limbs.
+- gcc13 vs gcc14 `lib_add` (64 limbs): 3.97 vs 2.63 c/l. Absolute GMP numbers (1.75 c/l add_n on x64) are above GMP's published
+ ~1.0-1.1 for this core class, so only the ratios should be trusted (Caveats).
+- Not measured at the probe level: exact division by 5 and the full Toom interpolation share of `divide_unsigned_short`;
+ the profile buckets do not isolate them. The 5.2x divexact-by-3 gap (14.3 vs 2.74 c/l) is real but its weight in mul
+ is unquantified.
+
+### 5.1 `add_n` hot loops (x64, -O2)
+
+g++-13 (no `__builtin_addc`: two `setb` and an `or` per limb, carry round-trips through a register):
+```
+mov (%rcx),%rdx ; movzbl %al,%eax ; add 0x1040(%rcx),%rdx ; setb %sil ; add %rax,%rdx ; setb %r8b
+mov %esi,%eax ; mov %rdx,0x2080(%rcx) ; add $8,%rcx ; or %r8d,%eax ; cmp ; jne
+```
+g++-14 (`adc`, but the carry is re-created every limb: `add $0xff,%al` ... `setb %al`):
+```
+add $0xff,%al ; mov 0x1040(%rdx),%rax ; adc (%rdx),%rax ; mov %rax,0x2080(%rdx) ; setb %al ; add $8,%rdx ; cmp ; jne
+```
+clang-23 (2x unrolled, `add $0xff,%sil` / `setb` / `movzbl` between the pairs):
+```
+mov (..),%r8 ; mov 8(..),%r9 ; add 0x1040(..),%r8 ; setb %r10b ; add %rsi,%r8 ; setb %sil ; or %r10b,%sil
+mov %r8,0x2080(..) ; add $0xff,%sil ; adc 0x1048(..),%r9 ; setb %r8b ; movzbl %r8b,%esi ; mov %r9,0x2088(..) ; ...
+```
+Inline-asm 4x (what GMP-class code does: four `adc` back to back, one carry materialization per 4 limbs):
+```
+mov (%rbx,%rsi,8),%r8 ; mov 8(..),%r9 ; mov 16(..),%r10 ; mov 24(..),%r11
+adc (%rdx,%rsi,8),%r8 ; adc 8(..),%r9 ; adc 16(..),%r10 ; adc 24(..),%r11 ; mov %r8,(%rdi,%rsi,8) ; ...
+```
+`tools/probe/run_x64.sh` writes the complete listings for every compiler and variant.
+
+## 6. Ranked optimization list
+
+Decision criteria applied (from the plan):
+- **Linear ops in asm** only if kernel/gmp >= 1.3x at 64-2000 limbs and linear helpers >= 10% of the mul/div/conversion
+ profiles: met on both archs (x64 1.5-2.4x add/sub, 2.0-2.5x shifts; M4 3.8-4.3x; helpers 24-54% inclusive in the
+ profiles above), but the "unrolled builtins first" clause applies and decides the order: on x64 `addcll_x4` is within 4-7% of the asm loop at 64 limbs, but 12-21% behind it at 512 limbs, so asm is
+ deferred (item 8), not ruled out.
+- **Front-end fixes first** where `auto - kernel` >= 30% of kernel time at <= 64 limbs: met for add/sub/shl/shr (~15 ns on
+ a 4-25 ns kernel), mul <= 16 limbs (21 ns on 5-80 ns), divrem 8x4 (66 ns on 44 ns); not met for gcd 2x2 (11%),
+ tochars 4x10 (auto already faster).
+- **Algorithmic items** flagged where the ratio grows with n: gcd (1.4 at 64 -> 24.6 at 65536 limbs), mul/sqr above 2000
+ limbs, divrem above 4000, conversions above 2000.
+
+| # | Item | Evidence | Expected gain | Effort | Arch |
+|---|---|---|---|---|---|
+| 1 | Cut fixed per-call cost of `+ - << >>`, small mul/div: inline capacity beyond one limb, avoid `construct_at` zero/store loops and the copy in `operator=`, avoid double zero-fill in `c = a * b` | auto - kernel 13-18 ns on add/sub/shift <= 16 limbs, ~21 ns mul <= 16; add 16x16 profile 43% alloc + 46% member code; add 1000 profile 33% `construct_at` + 11% memmove | add/sub/shl small 3.6-4.6x -> ~1.3-1.8x GMP (remove ~13-18 ns of 18-25); add 1000 on x64: auto 840 -> ~580 ns (kernel level) | S-M | both |
+| 2 | Rewrite `add/sub_unsigned_spans`, `shift_left_n/right_n`, `multiply_single_limb`, `submul_single_limb` as 4x-unrolled carry chains (builtin addc/subb), fix g++-13 fallback, keep one carry materialization per 4 limbs | probe: add 1.51x -> 1.12x at 64 limbs, 1.70x -> 1.16x at 512 (x64), 3.9x -> 1.7x (M4); gcc shifts 2.4-2.6x vs clang 0.7x; submul 1.5x/3.1x; mul_1 2.2x/1.9x | add/sub 64-2000 limbs 1.5-2.4x -> ~1.1-1.3x; mul 16384 -0.58 to -0.88 ms (2.14 -> 1.64-1.81); divrem 4096x2048 1.37 -> 1.0-1.14; tochars 10000 1.39 -> 1.03-1.15; fromchars 10000 1.85 -> 1.35-1.5; gcd 1024 2.2 -> 1.3-1.6 | S (add/sub) to M (mul_1/submul) | both |
+| 3 | Subquadratic gcd (HGCD / half-gcd) above ~1000-2000 limbs; keep Lehmer below | gcd auto/gmpz 2.2 (1k), 2.7 (2k), 9.2 (16k), 24.6 (65k) x64; M4 12.2/32.7; auto == kernel, so no front-end part | 16k limbs: 405 ms -> ~45 ms; 65k: 6.4 s -> ~0.26 s (GMP's times) | L | both |
+| 4 | Faster large multiplication: NTT/FFT implementation (65% `fft_ntt` + 26% `wide_ops` at 262144), Toom-8 / unbalanced and FFT crossovers for 16k-65k limbs; benefits sqr, divrem and conversions | mul 2.13x (16k), 2.85x (65k), 3.0x (1M); sqr 3.6x (1M); divrem 131072x65536 88% in multiply; tochars 262144 2.8x | up to 2-3x on mul/sqr >= 65k limbs and the large div/conversion rows that sit on them | L | both (M4: mul 3.4x at 16384, 2.5x at 65536, 1.9x at 262144, 1.3x at 1M; it has NEON NTT kernels) |
+| 5 | Re-tune Karatsuba/Toom/BZ cutoffs after #2, and use exact division (divexact by 3, 5) in Toom interpolation | mul 16384: our asm basecase 1.98 ms > GMP total 1.77 ms; mul ratio 0.6-0.9 (64-512) rising to 1.24 (2000) and 2.1 (16384); divexact_3 14.3 vs 2.74 c/l; BZ cutoff 160 leaves divrem 128x64..512x256 at 1.8-1.9x | not quantifiable before #2; the mul 2000-16384 band (1.24-2.13x) is the target; each cutoff change must be verified end to end | M | x64 first (IFMA table) |
+| 6 | AArch64 asm for add_n/sub_n/mul_1/submul_1 (`adcs`/`sbcs` chains) | M4 after unrolling: addcll_x4 still 1.7x GMP, submul 3.1x, mul_1 1.9x; no asm variant was probed on M4 | M4 add/sub 64-2000 limbs 3.8-4.3x -> ~1.0-1.7x; effect on M4 mul/div rows not estimated | M | AArch64 |
+| 7 | x64 `submul_1`/`mul_1` asm (mulx/adx) if #2 leaves them above ~1.3x GMP | probe 1.5x / 2.2x at 64 limbs | divrem 128x64..512x256 (91% submul): up to ~1.5x on those shapes | M | x64 |
+| 8 | x64 add/sub in asm: reconsider after #2 (re-measure the rewritten C++ at 512+ limbs) | probe add_n: `asm_adc_x4` 1.09x vs `addcll_x4` 1.12x at 64 limbs, but 1.02-1.03x vs 1.16-1.28x GMP at 512 limbs (gcc14 / clang23) | asm currently buys 12-21% at 512 limbs over the unrolled builtin (4-7% at 64); worth it only if #2 leaves add/sub > ~1.15x at 512+ limbs | M | x64 |
+| 9 | Make `add_unsigned_spans` stop at the carry tail (1024x1 add is 2.5x GMP in the kernel row) and give the public add the same compiler-independent loop | kernel/gmp 2.5x, auto 1.2x | kernel rows only; public path already fine | S | both |
+| 10 | Conversions: after #2 and #4, recheck; power-chain reuse looks minor at 10000 limbs (4.5-7%) but is untested above 100k limbs | tochars/fromchars 262144/131072 2.8x | speculative | M | both |
+
+Not recommended: asm for cmp (already faster than GMP on x64, parity on M4); asm for mul 32-512 limbs on x64 (IFMA beats
+GMP by 1.1-1.8x).
+
+## 7. Caveats
+
+- GMP's absolute `add_n` is ~1.75-2.0 c/l in both the probe and the sweep on x64, above its published ~1.0-1.1 on this
+ class of core (possible 4K aliasing or buffer placement shared by the probe and harness). Ratios are like-for-like;
+ absolute cycles are harness numbers.
+- M4 GMP `lshift` at 1000-4000 limbs x 13 bits is erratic (0.7-2 ns/limb); M4 shl/shr medium ratios are unreliable there.
+- 1M-limb add/sub rows are DRAM-bound with up to +-20% seed spread; M4 frequency is assumed (4.47 GHz), x64 pinned and
+ measured.
+- The classifier buckets are name-based heuristics; `span_ops incl.` counts any stack with a `span_ops` frame, so it
+ includes the `wide_ops` leaves beneath it. The `ns saved` table assumes a uniform speed-up r over the bucket.
+- The kernel shift rows include a copy-in; `kernelip` (in-place pair, in the CSVs for shl) does not. The gcd `kernel` row
+ exists only for square shapes (it skips the driver's Euclidean pre-steps). mul `kernel` = `unsliced`; auto may slice.
+- noIFMA changes the tuning table as well as the kernels, so it does not isolate the IFMA contribution at large sizes.
+ The g++-13 run covers only the linear ops. Only one clang (23.0.0 dev) and one GMP configuration per machine were used.
+- The flame-graph profiles are x64 only and use one shape per op/size class, averaged over the profiling run (medians in
+ 4.1 come from the profile run or the sweep as marked in `buckets.md`, so they can differ by a few percent from section 3).
+
+## 8. Appendix: reproducing
+
+The tools live in [`tools/`](tools/README.md): `gap_sweep.sh`, `selftest.sh`, `summarize.py`, `compare.py`,
+`probe_tables.py`, `bucket_tables.py`, `prof.sh`, `classify.py` and the standalone `tools/probe/` (see its README for inputs and
+outputs).
+
+Configure and build (any machine; `-DBEMAN_BIG_INT_SIMD_MUL=ON` for the numbers here; the harness is
+`beman.big_int.benchmarks.shape_sweep`):
+```
+# M4
+cmake --preset appleclang-release -B build/gmpgap/mac-release -DBEMAN_BIG_INT_BUILD_BENCHMARKS=ON \
+ -DBEMAN_BIG_INT_SHAPE_SWEEP_ONLY=ON -DBEMAN_BIG_INT_SWEEP_GMP=ON -DBEMAN_BIG_INT_SIMD_MUL=ON
+cmake --build build/gmpgap/mac-release --target beman.big_int.benchmarks.shape_sweep
+# x64, one build directory per configuration. infra/cmake/gnu-toolchain.cmake hard-codes `g++`, so for g++-14 put a
+# directory whose `g++` and `gcc` point at GCC 14 first on PATH. GMP 6.3.0 is found by the usual library search
+# (CMAKE_PREFIX_PATH= for a private build).
+C="-DBEMAN_BIG_INT_BUILD_BENCHMARKS=ON -DBEMAN_BIG_INT_SHAPE_SWEEP_ONLY=ON -DBEMAN_BIG_INT_SWEEP_GMP=ON -DBEMAN_BIG_INT_SIMD_MUL=ON"
+PATH=:$PATH cmake --preset gcc-release -B build/gmpgap/x64-gcc14 "-DCMAKE_CXX_FLAGS=-march=native" $C
+cmake --preset gcc-release -B build/gmpgap/x64-gcc13 "-DCMAKE_CXX_FLAGS=-march=native" $C # system g++-13
+cmake --preset llvm-release -B build/gmpgap/x64-clang "-DCMAKE_CXX_FLAGS=-march=native" $C # clang-23
+PATH=:$PATH cmake --preset gcc-release -B build/gmpgap/x64-noifma "-DCMAKE_CXX_FLAGS=-march=native" \
+ -DBEMAN_BIG_INT_X86_64_AVX512_IFMA=OFF $C
+for d in x64-gcc14 x64-gcc13 x64-clang x64-noifma; do cmake --build build/gmpgap/$d --target beman.big_int.benchmarks.shape_sweep; done
+```
+The numbers here were produced with an out-of-tree copy-and-build driver (`variant.sh`, not in the repo) that also checked
+the `#const` keys (`EXPECT="BMI2_ADX=1 AVX512_IFMA=1 SIMD_MUL=1"`, `AVX512_IFMA=0` for noIFMA); the equivalent check is
+reading the first `#const` line of each binary.
+
+Check the first `#const` line of each binary (`compiler=`, `BMI2_ADX=`, `AVX512_IFMA=`, `SIMD_MUL=`, `ndebug=1`).
+
+Run (paths relative to `tests/beman/big_int/perf`; the sweep driver encodes the size grid, rows per op, 9 rounds / 3 for the large band / 1 for gcd 65536):
+```
+tools/gap_sweep.sh out.csv --seed 1 [--ops add,sub,...] [--bands small,medium,large] [--pin "taskset -c 2"]
+tools/summarize.py a.csv [b.csv] [probe.csv] > summary.md # python3 -I; two CSVs => seed spread
+```
+The probe: `tools/probe/build_x64.sh` then `run_x64.sh` (x64), `build_mac.sh` then `run_mac.sh` (M4); `probe --check` is the
+correctness run.
+Single shapes by hand: `shape_sweep --rows auto,inplace,kernel,gmp,gmpz [--rounds N] [--round-ms X] [--seed S] SHAPE...`.
+Primary sweep: ~4.5 min per full run on either machine (gcc14 x64 268 s, M4 281 s); g++-13 linear ops 52 s; noIFMA 55 s.
+
+Profiles (x64 Linux, `kernel.perf_event_paranoid=1`, FlameGraph cloned to `$FLAMEGRAPH_DIR`):
+```
+tools/prof.sh profiles/ # all 25 shape/row pairs; add a substring to run a subset
+tools/classify.py profiles/ gap_x64_gcc14_seed1.csv > profiles/buckets.md
+tools/bucket_tables.py profiles/buckets.md
+```
+`prof.sh` records `perf record -q -e cycles:u -F 2000 --call-graph lbr` under `taskset -c 2` with
+`--rows ROW --rounds 1 --round-ms 4000 --seed 1`, folds with `perf script --inline | stackcollapse-perf.pl`, strips template
+arguments and parameter lists, and renders with `flamegraph.pl --minwidth 0.5 --width 1200`. ROW is `auto` and `gmpz` (and
+`inplace` for add 1000). `MINW` overrides the minwidth (not overridden for any of the 25 SVGs; largest SVG 464 KB).
+`classify.py` buckets the folded stacks by the frame nearest the leaf and scales by the median.
diff --git a/tests/beman/big_int/perf/gmp_gap_m4.csv b/tests/beman/big_int/perf/gmp_gap_m4.csv
new file mode 100644
index 00000000..df58224b
--- /dev/null
+++ b/tests/beman/big_int/perf/gmp_gap_m4.csv
@@ -0,0 +1,522 @@
+# gap_sweep date 2026-10-06T13:53:20Z host Matts-MacBook-Pro.local Darwin arm64
+# gap_sweep bin /Users/mattborland/Documents/std-big-int/build/gmpgap/mac-release/tests/beman/big_int/perf/beman.big_int.benchmarks.shape_sweep seed=1 ops=add,sub,shl,shr,cmp,mul,sqr,divrem,tochars,fromchars,gcd bands=small,medium,large pin=
+#const square_long_cutoff=5 karatsuba_cutoff=112 karatsuba_fallback=80 square_karatsuba_cutoff=168 toom_cook_3_cutoff=300 toom_cook_4_cutoff=1400 toom_cook_6_5_cutoff=1400 toom_cook_8_5_cutoff=15000 square_toom_cook_3_cutoff=500 square_toom_cook_4_cutoff=2000 square_toom_cook_6_5_cutoff=2400 square_toom_cook_8_5_cutoff=24000 fft_mul_min_limbs=3300 fft_mul_model_num=5 fft_mul_model_den=8 square_fft_min_limbs=3300 square_fft_model_num=7 square_fft_model_den=16 fft_model_log_power=1 fft_model_root_degree=2 fft_cyclic_cutoff=8192 multiply_mod_bnm1_cutoff=16 mul_slice_z0_min=112 mul_slice_z0_num=7 mul_slice_z0_den=4 mul_slice_z1_min=300 mul_slice_z1_num=3 mul_slice_z1_den=2 mul_slice_z2_min=1400 mul_slice_z2_num=3 mul_slice_z2_den=2 mul_slice_z3_min=0 mul_slice_z3_num=3 mul_slice_z3_den=2 mul_slice_z4_min=0 mul_slice_z4_num=3 mul_slice_z4_den=2 mul_slice_z5_min=0 mul_slice_z5_num=3 mul_slice_z5_den=2 mul_slice_zone_count=3 burnikel_ziegler_cutoff=40 burnikel_ziegler_offset=10 barrett_march_cutoff=9 barrett_march8_cutoff=2048 barrett_quarter_cutoff=49152 barrett_balanced_cutoff=131072 reciprocal_span_cutoff=64 fast_input_basecase_chunks=16 fast_output_basecase_chunks=16 fast_input_charconv_min_chunks=3 fast_output_preinv_min_limbs=65536 ARCH=aarch64 BMI2_ADX=0 AVX512_IFMA=0 SIMD_MUL=1 limb_bits=64 ndebug=1 optimize=1 isa_bmi2=0 isa_adx=0 isa_avx2=0 isa_avx512f=0 isa_avx512ifma=0 compiler=appleclang-21.0.0_(clang-2100.3.34.2) via=cmake build_type=RelWithDebInfo cxx_flags=-O3__-O2_-g_-DNDEBUG_defs:
+op,path,la,lb,reps,rounds,median_ns,min_ns,round_ms_actual
+add,auto,1,1,3107524,9,6.37,6.16,19.70
+add,inplace,1,1,3204242,9,3.34,3.14,21.15
+add,kernel,1,1,17062016,9,1.15,1.14,19.66
+add,gmp,1,1,13447369,9,1.48,1.45,19.87
+add,gmpz,1,1,6749869,9,2.95,2.89,20.36
+add,auto,2,2,1402070,9,14.42,14.23,20.30
+add,inplace,2,2,2665229,9,3.81,3.78,20.42
+add,kernel,2,2,14261965,9,1.40,1.39,19.93
+add,gmp,2,2,10623415,9,1.94,1.76,20.37
+add,gmpz,2,2,6061289,9,3.37,3.11,22.16
+add,auto,4,4,1311740,9,15.03,14.57,19.69
+add,inplace,4,4,2774747,9,3.84,3.59,20.87
+add,kernel,4,4,10426257,9,1.94,1.91,20.16
+add,gmp,4,4,11399558,9,1.80,1.76,21.75
+add,gmpz,4,4,5936154,9,4.01,3.32,21.99
+add,auto,8,8,1311096,9,15.30,15.11,20.19
+add,inplace,8,8,1972738,9,4.95,4.88,19.69
+add,kernel,8,8,5774217,9,3.52,3.49,20.30
+add,gmp,8,8,8888892,9,2.24,2.18,19.80
+add,gmpz,8,8,5284167,9,5.00,3.82,24.09
+add,auto,16,16,1228015,9,16.37,16.28,20.15
+add,inplace,16,16,1041591,9,9.70,9.50,20.23
+add,kernel,16,16,2742825,9,7.44,7.27,20.63
+add,gmp,16,16,6179516,9,3.24,3.21,20.01
+add,gmpz,16,16,4850123,9,4.02,3.99,19.85
+add,auto,64,64,358390,9,53.56,52.91,19.31
+add,inplace,64,64,198234,9,50.07,49.86,19.87
+add,kernel,64,64,415852,9,47.86,46.87,19.90
+add,gmp,64,64,1763131,9,11.35,11.08,19.95
+add,gmpz,64,64,1515007,9,13.19,12.99,19.94
+add,auto,256,256,140538,9,142.55,137.70,20.00
+add,inplace,256,256,43588,9,228.55,221.11,19.82
+add,kernel,256,256,88568,9,221.56,218.49,19.67
+add,gmp,256,256,357563,9,56.37,55.64,20.09
+add,gmpz,256,256,351117,9,56.74,55.98,19.90
+add,auto,1024,1024,37217,9,537.94,523.87,20.04
+add,inplace,1024,1024,10574,9,946.99,916.50,20.01
+add,kernel,1024,1024,21815,9,918.50,906.26,20.05
+add,gmp,1024,1024,80153,9,245.95,240.81,19.67
+add,gmpz,1024,1024,82806,9,246.59,240.68,20.35
+add,auto,2000,2000,18535,9,1085.14,1006.86,20.12
+add,inplace,2000,2000,5380,9,1845.39,1821.01,19.91
+add,kernel,2000,2000,11146,9,1810.21,1775.89,20.15
+add,gmp,2000,2000,41075,9,484.63,475.33,19.92
+add,gmpz,2000,2000,41303,9,485.26,464.64,19.93
+add,auto,1024,1,111928,9,170.89,166.48,19.62
+add,inplace,1024,1,10800,9,926.89,917.99,20.08
+add,kernel,1024,1,28441,9,709.14,692.65,20.40
+add,gmp,1024,1,77472,9,264.32,251.95,20.52
+add,gmpz,1024,1,76671,9,258.68,253.05,19.79
+add,auto,16384,16384,2182,3,9212.94,9167.26,20.08
+add,inplace,16384,16384,622,3,16457.09,16239.78,20.38
+add,kernel,16384,16384,1311,3,15211.19,15048.50,19.90
+add,gmp,16384,16384,4788,3,4130.18,3951.50,19.52
+add,gmpz,16384,16384,4872,3,4136.43,4121.95,20.17
+add,auto,131072,131072,277,3,72592.06,71128.91,20.09
+add,inplace,131072,131072,80,3,124282.03,123633.59,19.96
+add,kernel,131072,131072,164,3,122596.29,121007.62,20.21
+add,gmp,131072,131072,614,3,32935.40,32574.71,20.36
+add,gmpz,131072,131072,577,3,33075.53,32867.85,19.20
+add,auto,1048576,1048576,37,3,548613.73,543707.22,20.24
+add,inplace,1048576,1048576,11,3,986551.14,964651.50,21.56
+add,kernel,1048576,1048576,21,3,980063.52,964527.76,20.59
+add,gmp,1048576,1048576,76,3,262639.25,257496.16,19.83
+add,gmpz,1048576,1048576,78,3,262104.71,258674.14,20.40
+sub,auto,1,1,2682777,9,7.37,7.14,19.71
+sub,inplace,1,1,3165423,9,3.48,3.41,22.19
+sub,kernel,1,1,19113000,9,1.05,1.05,20.11
+sub,gmp,1,1,13584925,9,1.49,1.47,20.24
+sub,gmpz,1,1,7995520,9,2.49,2.48,20.03
+sub,auto,2,2,1383309,9,14.86,14.54,20.53
+sub,inplace,2,2,2973456,9,3.36,3.11,19.58
+sub,kernel,2,2,14259413,9,1.40,1.39,20.00
+sub,gmp,2,2,11652553,9,1.78,1.73,20.29
+sub,gmpz,2,2,6790973,9,2.97,2.75,19.71
+sub,auto,4,4,1292694,9,15.50,15.35,20.06
+sub,inplace,4,4,2714081,9,3.84,3.73,21.00
+sub,kernel,4,4,10321097,9,1.95,1.93,20.14
+sub,gmp,4,4,11454748,9,1.77,1.73,20.24
+sub,gmpz,4,4,7215377,9,2.78,2.73,20.08
+sub,auto,8,8,1271427,9,16.31,15.79,20.70
+sub,inplace,8,8,1873716,9,5.23,5.04,19.54
+sub,kernel,8,8,5690272,9,3.53,3.52,20.11
+sub,gmp,8,8,8969085,9,2.25,2.23,20.41
+sub,gmpz,8,8,5985120,9,3.36,3.27,20.27
+sub,auto,16,16,1061845,9,18.25,18.10,19.52
+sub,inplace,16,16,1090182,9,9.79,9.25,20.41
+sub,kernel,16,16,2601990,9,7.62,7.54,19.83
+sub,gmp,16,16,6278528,9,3.24,3.23,20.37
+sub,gmpz,16,16,4632464,9,4.25,4.22,19.68
+sub,auto,64,64,479956,9,41.55,40.84,20.11
+sub,inplace,64,64,198663,9,50.81,49.35,20.29
+sub,kernel,64,64,411242,9,48.28,46.90,20.09
+sub,gmp,64,64,1834218,9,10.90,10.78,20.00
+sub,gmpz,64,64,1480506,9,13.81,13.41,20.36
+sub,auto,256,256,153189,9,132.35,131.46,20.29
+sub,inplace,256,256,43616,9,229.43,225.77,19.97
+sub,kernel,256,256,89189,9,224.74,219.65,19.98
+sub,gmp,256,256,360700,9,56.13,55.33,20.26
+sub,gmpz,256,256,338629,9,57.42,56.19,19.41
+sub,auto,1024,1024,36557,9,539.71,521.50,19.96
+sub,inplace,1024,1024,10562,9,961.46,925.89,20.19
+sub,kernel,1024,1024,21414,9,929.81,911.49,19.89
+sub,gmp,1024,1024,80609,9,246.51,238.27,19.77
+sub,gmpz,1024,1024,80110,9,249.40,240.97,19.88
+sub,auto,2000,2000,17821,9,1062.43,1013.83,18.96
+sub,inplace,2000,2000,5407,9,1874.93,1827.15,20.21
+sub,kernel,2000,2000,10888,9,1825.40,1779.52,20.14
+sub,gmp,2000,2000,41291,9,484.24,469.03,19.92
+sub,gmpz,2000,2000,40823,9,489.72,478.18,19.93
+sub,auto,1024,1,91559,9,222.50,205.70,20.47
+sub,inplace,1024,1,10319,9,941.41,913.64,19.33
+sub,kernel,1024,1,29253,9,701.59,680.75,20.46
+sub,gmp,1024,1,77996,9,256.79,250.89,19.96
+sub,gmpz,1024,1,79417,9,258.77,255.14,20.56
+sub,auto,16384,16384,2161,3,9248.57,9248.01,20.02
+sub,inplace,16384,16384,664,3,15433.99,15342.21,20.46
+sub,kernel,16384,16384,1294,3,15386.91,15359.70,19.90
+sub,gmp,16384,16384,4819,3,4132.57,4122.16,19.93
+sub,gmpz,16384,16384,4936,3,4030.01,3986.43,19.83
+sub,auto,131072,131072,275,3,71822.57,71586.97,19.86
+sub,inplace,131072,131072,81,3,128394.29,127865.99,21.06
+sub,kernel,131072,131072,164,3,123943.85,121213.66,20.20
+sub,gmp,131072,131072,604,3,32513.25,32141.76,19.65
+sub,gmpz,131072,131072,616,3,32088.00,31835.63,19.80
+sub,auto,1048576,1048576,36,3,551949.08,539949.08,19.79
+sub,inplace,1048576,1048576,11,3,990700.77,990121.23,21.84
+sub,kernel,1048576,1048576,21,3,998408.71,992234.10,21.15
+sub,gmp,1048576,1048576,77,3,260472.95,260332.26,20.15
+sub,gmpz,1048576,1048576,77,3,262102.82,261879.87,20.18
+shl,auto,1,13,1107786,9,18.15,17.90,20.06
+shl,inplace,1,13,4361732,9,2.31,2.30,20.25
+shl,kernel,1,13,8047338,9,2.49,2.40,20.12
+shl,kernelip,1,13,8483052,9,1.49,1.30,22.64
+shl,gmp,1,13,16341062,9,1.26,1.25,20.58
+shl,gmpz,1,13,9795770,9,2.03,2.01,19.85
+shl,auto,4,13,974614,9,20.70,19.84,20.30
+shl,inplace,4,13,2741031,9,3.59,3.59,19.83
+shl,kernel,4,13,5321334,9,3.74,3.66,19.89
+shl,kernelip,4,13,3042709,9,3.38,3.28,20.45
+shl,gmp,4,13,10103485,9,2.00,1.96,20.17
+shl,gmpz,4,13,7346938,9,2.73,2.72,20.11
+shl,auto,16,13,903838,9,22.33,21.82,20.16
+shl,inplace,16,13,1743893,9,5.82,5.75,20.31
+shl,kernel,16,13,4921363,9,4.09,4.04,20.16
+shl,kernelip,16,13,1490690,9,6.75,6.58,20.47
+shl,gmp,16,13,5647196,9,4.08,3.47,22.50
+shl,gmpz,16,13,3891920,9,5.25,5.22,20.51
+shl,auto,256,13,217676,9,87.92,87.57,19.16
+shl,inplace,256,13,274290,9,36.68,36.48,20.17
+shl,kernel,256,13,400570,9,48.66,48.29,19.50
+shl,kernelip,256,13,210581,9,48.74,48.43,20.53
+shl,gmp,256,13,538219,9,36.78,36.72,19.83
+shl,gmpz,256,13,524080,9,37.94,37.84,19.90
+shl,auto,2000,13,35056,9,647.15,586.05,19.83
+shl,inplace,2000,13,35724,9,282.45,279.42,20.16
+shl,kernel,2000,13,48980,9,405.86,398.13,19.87
+shl,kernelip,2000,13,26504,9,375.67,368.24,19.83
+shl,gmp,2000,13,47534,9,1235.98,731.77,40.10
+shl,gmpz,2000,13,27758,9,1639.18,957.09,45.75
+shl,auto,2000,77,27179,9,801.36,772.26,21.74
+shl,inplace,2000,77,23318,9,425.89,418.72,19.86
+shl,kernel,2000,77,35542,9,574.09,567.22,20.37
+shl,kernelip,2000,77,19020,9,514.23,509.08,19.60
+shl,gmp,2000,77,68981,9,289.86,287.05,20.23
+shl,gmpz,2000,77,66700,9,290.67,287.35,19.41
+shl,auto,131072,13,407,3,49092.65,47995.60,19.83
+shl,inplace,131072,13,515,3,19890.49,19835.15,20.49
+shl,kernel,131072,13,683,3,29952.36,29614.63,20.56
+shl,kernelip,131072,13,398,3,24888.66,24393.79,19.80
+shl,gmp,131072,13,1003,3,20007.93,19807.08,20.01
+shl,gmpz,131072,13,996,3,20506.19,20305.81,20.41
+shl,auto,1048576,13,50,3,413575.84,405018.34,20.65
+shl,inplace,1048576,13,59,3,162682.55,162664.54,19.20
+shl,kernel,1048576,13,83,3,236195.78,235800.71,19.69
+shl,kernelip,1048576,13,50,3,200430.84,199080.83,20.02
+shl,gmp,1048576,13,130,3,155264.42,154957.69,20.18
+shl,gmpz,1048576,13,128,3,156634.44,156102.87,20.08
+shr,auto,1,13,2057328,9,9.58,9.49,19.85
+shr,inplace,1,13,4221379,9,2.24,2.19,19.06
+shr,kernel,1,13,8054800,9,2.49,2.43,19.96
+shr,gmp,1,13,20000001,9,0.99,0.99,19.86
+shr,gmpz,1,13,10077054,9,1.99,1.97,20.13
+shr,auto,4,13,1086074,9,18.43,18.03,20.14
+shr,inplace,4,13,3020135,9,3.24,3.22,19.64
+shr,kernel,4,13,5019178,9,3.96,3.88,19.83
+shr,gmp,4,13,11467456,9,1.74,1.72,19.97
+shr,gmpz,4,13,7254849,9,2.74,2.73,19.97
+shr,auto,16,13,915647,9,21.11,20.78,19.29
+shr,inplace,16,13,1977779,9,5.07,5.04,20.06
+shr,kernel,16,13,4858333,9,4.11,4.10,20.00
+shr,gmp,16,13,6216269,9,3.22,3.20,20.05
+shr,gmpz,16,13,4724089,9,4.27,4.22,20.09
+shr,auto,256,13,230352,9,86.32,85.55,19.91
+shr,inplace,256,13,279582,9,35.95,35.63,20.14
+shr,kernel,256,13,292583,9,67.29,66.82,19.75
+shr,gmp,256,13,537792,9,37.68,37.18,20.23
+shr,gmpz,256,13,525201,9,38.10,37.99,20.04
+shr,auto,2000,13,34361,9,633.15,593.51,22.34
+shr,inplace,2000,13,35323,9,282.24,281.03,19.95
+shr,kernel,2000,13,38825,9,510.91,500.12,19.76
+shr,gmp,2000,13,68947,9,304.03,289.56,21.43
+shr,gmpz,2000,13,66951,9,305.40,292.86,19.98
+shr,auto,2000,77,19111,9,1048.10,1026.23,20.14
+shr,inplace,2000,77,23813,9,419.59,415.78,19.97
+shr,kernel,2000,77,38632,9,512.23,498.69,19.76
+shr,gmp,2000,77,68140,9,289.82,289.58,19.87
+shr,gmpz,2000,77,68240,9,287.26,285.43,19.63
+shr,auto,131072,13,400,3,50237.19,48724.69,19.90
+shr,inplace,131072,13,502,3,19789.47,19465.06,19.78
+shr,kernel,131072,13,516,3,38218.99,38052.73,19.77
+shr,gmp,131072,13,1012,3,19879.86,19875.86,20.16
+shr,gmpz,131072,13,998,3,20006.14,19996.58,20.09
+shr,auto,1048576,13,50,3,413661.66,403371.66,20.78
+shr,inplace,1048576,13,62,3,161820.23,161712.70,20.11
+shr,kernel,1048576,13,67,3,299976.37,299203.99,20.14
+shr,gmp,1048576,13,131,3,154502.23,154303.75,20.38
+shr,gmpz,1048576,13,132,3,152279.98,152176.14,20.10
+cmp,auto,1,1,12095931,9,1.66,1.65,20.09
+cmp,kernel,1,1,20000001,9,1.15,0.99,19.93
+cmp,gmp,1,1,19895426,9,0.99,0.97,19.68
+cmp,gmpz,1,1,16178921,9,1.24,1.24,20.17
+cmp,auto,4,4,8968091,9,2.74,2.23,23.79
+cmp,kernel,4,4,10988068,9,1.75,1.73,19.86
+cmp,gmp,4,4,11486581,9,1.72,1.68,19.71
+cmp,gmpz,4,4,9020866,9,2.30,2.25,22.70
+cmp,auto,16,16,3549207,9,5.73,5.60,20.36
+cmp,kernel,16,16,3524570,9,5.33,5.18,19.03
+cmp,gmp,16,16,3803853,9,5.28,5.20,20.10
+cmp,gmpz,16,16,3453762,9,5.81,5.69,20.00
+cmp,auto,256,256,253107,9,79.18,77.98,19.99
+cmp,kernel,256,256,254169,9,81.33,79.56,20.66
+cmp,gmp,256,256,286248,9,71.75,69.72,20.50
+cmp,gmpz,256,256,264702,9,78.43,75.97,20.69
+cmp,auto,2000,2000,36321,9,672.85,496.79,21.32
+cmp,kernel,2000,2000,28630,9,702.18,664.75,19.88
+cmp,gmp,2000,2000,40431,9,499.00,489.99,20.18
+cmp,gmpz,2000,2000,29991,9,683.75,676.80,20.50
+cmp,auto,131072,131072,432,3,46917.44,46750.39,20.33
+cmp,kernel,131072,131072,424,3,45394.75,44343.16,19.18
+cmp,gmp,131072,131072,437,3,45177.06,45129.67,19.74
+cmp,gmpz,131072,131072,444,3,45258.26,44394.89,20.04
+mul,auto,1,1,1341490,9,15.04,14.93,20.24
+mul,gmp,1,1,5361605,9,3.72,3.64,19.94
+mul,gmpz,1,1,7282526,9,2.73,2.72,19.90
+mul,auto,2,2,783605,9,27.65,25.05,19.25
+mul,kernel,2,2,3225640,9,6.03,5.99,19.67
+mul,gmp,2,2,4265559,9,4.49,4.47,19.67
+mul,gmpz,2,2,2924320,9,6.79,6.73,20.01
+mul,auto,3,3,674507,9,29.32,29.07,19.86
+mul,kernel,3,3,2487050,9,8.20,7.93,20.24
+mul,gmp,3,3,3195813,9,6.24,6.20,20.03
+mul,gmpz,3,3,1936523,9,10.77,10.18,20.76
+mul,auto,4,4,683978,9,29.28,29.09,20.22
+mul,kernel,4,4,1804468,9,11.13,10.72,20.02
+mul,gmp,4,4,2219481,9,9.19,8.95,20.32
+mul,gmpz,4,4,2050693,9,9.78,9.70,20.43
+mul,auto,6,6,608259,9,33.31,32.88,20.22
+mul,kernel,6,6,1055961,9,19.33,18.84,20.35
+mul,gmp,6,6,1297486,9,15.40,14.66,19.80
+mul,gmpz,6,6,1160598,9,18.19,17.29,21.37
+mul,auto,8,8,475436,9,41.67,41.22,19.82
+mul,kernel,8,8,847661,9,23.52,23.39,19.91
+mul,gmp,8,8,774993,9,25.40,24.45,19.75
+mul,gmpz,8,8,819231,9,24.62,24.48,20.38
+mul,auto,12,12,320199,9,62.06,61.73,19.91
+mul,kernel,12,12,471760,9,42.19,41.68,20.12
+mul,gmp,12,12,451068,9,44.54,43.98,20.10
+mul,gmpz,12,12,430853,9,46.76,46.25,20.17
+mul,auto,16,16,209137,9,95.87,94.39,20.00
+mul,kernel,16,16,284612,9,70.17,69.69,20.03
+mul,gmp,16,16,263706,9,75.99,75.66,20.28
+mul,gmpz,16,16,257834,9,79.06,78.61,20.40
+mul,auto,32,32,66096,9,301.31,297.25,19.86
+mul,kernel,32,32,77095,9,259.53,258.66,20.00
+mul,gmp,32,32,75537,9,267.46,263.41,20.16
+mul,gmpz,32,32,73106,9,271.28,267.94,19.84
+mul,auto,64,64,19188,9,1036.72,1032.73,19.93
+mul,kernel,64,64,20210,9,986.53,983.02,19.98
+mul,gmp,64,64,25621,9,774.43,766.79,19.82
+mul,gmpz,64,64,25820,9,777.67,772.14,20.06
+mul,auto,128,128,5531,9,3675.11,3615.97,20.27
+mul,kernel,128,128,5610,9,3580.85,3561.85,20.19
+mul,gmp,128,128,7226,9,2761.17,2719.28,20.05
+mul,gmpz,128,128,7167,9,2764.95,2727.86,19.83
+mul,auto,256,256,1673,9,11944.11,11840.93,20.03
+mul,kernel,256,256,1667,9,12039.07,11957.76,20.20
+mul,gmp,256,256,2542,9,7927.14,7687.25,20.09
+mul,gmpz,256,256,2523,9,7984.05,7919.94,20.22
+mul,auto,512,512,536,9,37436.88,37136.43,20.06
+mul,kernel,512,512,542,9,37027.75,36509.61,20.04
+mul,gmp,512,512,1052,9,19067.85,18722.91,20.15
+mul,gmpz,512,512,1055,9,19094.04,18902.69,20.17
+mul,auto,1000,1000,185,9,107305.18,106206.30,19.85
+mul,kernel,1000,1000,186,9,107057.12,105831.54,19.92
+mul,gmp,1000,1000,360,9,55279.40,53951.97,19.85
+mul,gmpz,1000,1000,362,9,55172.77,54575.39,20.06
+mul,auto,2000,2000,66,9,303312.50,297138.26,20.10
+mul,kernel,2000,2000,66,9,304207.08,297984.85,20.05
+mul,gmp,2000,2000,134,9,148890.54,147257.16,19.95
+mul,gmpz,2000,2000,134,9,150290.43,148013.69,20.26
+mul,auto,2000,100,423,9,48638.40,47718.48,20.52
+mul,kernel,2000,100,420,9,47352.98,46721.63,19.93
+mul,gmp,2000,100,551,9,36213.55,35674.08,19.94
+mul,gmpz,2000,100,565,9,35606.56,35000.15,20.11
+mul,auto,4096,4096,23,3,877572.43,872936.61,20.18
+mul,kernel,4096,4096,23,3,873938.39,853009.04,20.02
+mul,gmp,4096,4096,60,3,341079.17,340453.47,20.48
+mul,gmpz,4096,4096,61,3,329944.67,328820.36,20.17
+mul,auto,16384,16384,4,3,5335520.75,5280531.25,21.28
+mul,kernel,16384,16384,4,3,5308000.00,5306656.25,21.23
+mul,gmp,16384,16384,13,3,1586176.31,1583653.85,20.68
+mul,gmpz,16384,16384,14,3,1548199.36,1542514.93,21.67
+mul,auto,65536,65536,1,3,21955083.00,21939041.00,21.96
+mul,kernel,65536,65536,1,3,22166542.00,21934417.00,22.11
+mul,gmp,65536,65536,3,3,8750916.67,8706930.33,26.24
+mul,gmpz,65536,65536,3,3,8713708.33,8688764.00,26.12
+mul,auto,262144,262144,1,3,94047125.00,93480000.00,94.19
+mul,kernel,262144,262144,1,3,94153916.00,93311500.00,94.22
+mul,gmp,262144,262144,1,3,54927334.00,54261666.00,54.74
+mul,gmpz,262144,262144,1,3,49332917.00,48816625.00,49.17
+mul,auto,1048576,1048576,1,3,395598625.00,388561500.00,394.94
+mul,kernel,1048576,1048576,1,3,392164167.00,391152875.00,394.99
+mul,gmp,1048576,1048576,1,3,295858417.00,294879667.00,296.43
+mul,gmpz,1048576,1048576,1,3,301662834.00,297408125.00,300.95
+mul,auto,262144,8192,1,3,45424708.00,45349792.00,45.42
+mul,kernel,262144,8192,1,3,47406292.00,46411959.00,47.36
+mul,gmp,262144,8192,2,3,17159833.00,17156729.00,34.35
+mul,gmpz,262144,8192,2,3,16999750.00,16888854.50,34.04
+sqr,auto,4,4,685278,9,29.45,29.05,20.12
+sqr,kernel,4,4,1955516,9,10.54,9.68,20.52
+sqr,gmp,4,4,2971750,9,6.33,6.20,19.46
+sqr,gmpz,4,4,2225716,9,9.43,8.94,20.94
+sqr,auto,16,16,220769,9,88.64,88.20,19.64
+sqr,kernel,16,16,324502,9,61.40,61.10,19.91
+sqr,gmp,16,16,314557,9,63.42,60.15,19.69
+sqr,gmpz,16,16,303360,9,66.08,65.35,20.13
+sqr,auto,64,64,31885,9,626.51,624.12,20.03
+sqr,kernel,64,64,34300,9,580.16,577.90,19.92
+sqr,gmp,64,64,29658,9,679.80,669.11,20.11
+sqr,gmpz,64,64,29706,9,671.00,646.43,19.86
+sqr,auto,512,512,806,9,24587.68,24473.74,19.81
+sqr,kernel,512,512,813,9,24602.45,24559.45,20.02
+sqr,gmp,512,512,1085,9,18431.18,18035.87,19.95
+sqr,gmpz,512,512,1231,9,16393.04,16294.31,20.35
+sqr,auto,2000,2000,88,9,226823.40,225176.14,20.04
+sqr,kernel,2000,2000,92,9,223043.92,221443.39,20.50
+sqr,gmp,2000,2000,170,9,121215.20,120265.69,20.63
+sqr,gmpz,2000,2000,166,9,120783.63,118275.10,20.01
+sqr,auto,16384,16384,5,3,4269483.40,4262783.40,21.40
+sqr,kernel,16384,16384,5,3,4259666.60,4211525.00,21.28
+sqr,gmp,16384,16384,18,3,1129162.06,1128000.00,20.35
+sqr,gmpz,16384,16384,18,3,1135155.06,1133724.56,20.52
+sqr,auto,262144,262144,1,3,78928417.00,77924458.00,78.72
+sqr,kernel,262144,262144,1,3,77783167.00,77556375.00,77.75
+sqr,gmp,262144,262144,1,3,32141208.00,31929292.00,32.13
+sqr,gmpz,262144,262144,1,3,32585625.00,32572417.00,32.64
+sqr,auto,1048576,1048576,1,3,325923292.00,325661584.00,326.19
+sqr,kernel,1048576,1048576,1,3,325641375.00,323991792.00,325.11
+sqr,gmp,1048576,1048576,1,3,212211625.00,211420250.00,212.01
+sqr,gmpz,1048576,1048576,1,3,210999834.00,210551875.00,211.48
+divrem,auto,2,1,676233,9,29.77,29.68,20.21
+divrem,kernel,2,1,3559258,9,5.77,5.68,20.57
+divrem,gmp,2,1,3848833,9,5.16,5.12,19.95
+divrem,gmpz,2,1,2270521,9,8.82,8.70,20.04
+divrem,auto,8,4,211339,9,91.65,90.85,19.39
+divrem,kernel,8,4,493050,9,40.07,39.21,19.78
+divrem,gmp,8,4,507520,9,39.96,38.59,20.24
+divrem,gmpz,8,4,474546,9,42.40,41.51,20.11
+divrem,auto,32,16,41600,9,472.38,465.18,19.67
+divrem,kernel,32,16,51740,9,385.14,379.73,19.93
+divrem,gmp,32,16,97274,9,204.83,199.40,19.91
+divrem,gmpz,32,16,96391,9,207.31,204.11,20.00
+divrem,auto,128,64,5472,9,3666.86,3572.51,20.07
+divrem,kernel,128,64,5667,9,3497.38,3421.19,19.75
+divrem,gmp,128,64,11243,9,1784.23,1752.19,20.02
+divrem,gmpz,128,64,11105,9,1771.88,1731.05,19.71
+divrem,auto,512,256,676,9,29644.97,29040.62,20.04
+divrem,kernel,512,256,680,9,29341.85,28843.87,19.95
+divrem,gmp,512,256,1221,9,16535.46,16312.69,20.14
+divrem,gmpz,512,256,1196,9,16573.82,16317.27,19.83
+divrem,auto,2000,1000,85,9,236967.15,229596.08,20.09
+divrem,kernel,2000,1000,86,9,232389.06,227571.70,20.08
+divrem,gmp,2000,1000,144,9,136705.44,136237.56,19.72
+divrem,gmpz,2000,1000,140,9,144735.42,140609.52,20.17
+divrem,auto,4000,2000,29,9,687788.79,679546.00,19.92
+divrem,kernel,4000,2000,30,9,682583.33,681412.50,20.51
+divrem,gmp,4000,2000,59,9,344270.47,342912.42,20.32
+divrem,gmpz,4000,2000,56,9,358528.29,356811.75,20.12
+divrem,auto,2000,100,211,9,94863.15,94490.92,20.06
+divrem,kernel,2000,100,212,9,94261.00,93548.74,19.99
+divrem,gmp,2000,100,290,9,69303.16,68833.62,20.12
+divrem,gmpz,2000,100,289,9,69232.84,68396.34,20.00
+divrem,auto,65536,1,83,9,241435.73,238596.39,20.09
+divrem,kernel,65536,1,90,9,225797.69,222451.86,20.38
+divrem,gmp,65536,1,118,9,168371.47,167134.89,19.88
+divrem,gmpz,65536,1,117,9,169690.88,168385.68,19.91
+divrem,auto,16384,8192,4,3,6014218.75,6014010.50,24.08
+divrem,kernel,16384,8192,4,3,6091135.50,6050614.50,24.52
+divrem,gmp,16384,8192,11,3,1867769.00,1854272.73,20.60
+divrem,gmpz,16384,8192,11,3,1960511.36,1948765.18,21.53
+divrem,auto,131072,65536,1,3,121020125.00,120834500.00,121.08
+divrem,kernel,131072,65536,1,3,121194000.00,120916417.00,121.15
+divrem,gmp,131072,65536,1,3,22066667.00,21988875.00,22.07
+divrem,gmpz,131072,65536,1,3,21943584.00,21943375.00,22.04
+divrem,auto,1048576,524288,1,3,887578708.00,884893334.00,889.93
+divrem,kernel,1048576,524288,1,3,881450833.00,879140208.00,881.28
+divrem,gmp,1048576,524288,1,3,294489959.00,294341583.00,294.89
+divrem,gmpz,1048576,524288,1,3,279773125.00,279510250.00,280.61
+tochars,auto,1,10,3229160,9,6.37,6.21,20.45
+tochars,kernel,1,10,603127,9,33.08,32.20,19.88
+tochars,gmp,1,10,1152851,9,17.37,17.11,20.01
+tochars,gmpz,1,10,780700,9,25.72,25.39,20.08
+tochars,auto,4,10,259693,9,77.26,76.71,20.13
+tochars,kernel,4,10,204861,9,102.54,98.24,20.88
+tochars,gmp,4,10,278520,9,72.02,70.94,20.18
+tochars,gmpz,4,10,213157,9,93.85,90.75,19.95
+tochars,auto,16,10,25061,9,816.54,796.13,20.40
+tochars,kernel,16,10,27998,9,717.87,705.92,20.18
+tochars,gmp,16,10,40229,9,496.68,482.04,19.89
+tochars,gmpz,16,10,33656,9,598.41,575.38,20.17
+tochars,auto,64,10,4327,9,4676.42,4576.38,20.30
+tochars,kernel,64,10,4648,9,4355.93,4261.95,20.22
+tochars,gmp,64,10,7158,9,2798.15,2757.34,20.02
+tochars,gmpz,64,10,6339,9,3129.68,3118.59,19.86
+tochars,auto,256,10,605,9,33688.02,32907.85,20.32
+tochars,kernel,256,10,619,9,32238.22,31308.29,19.86
+tochars,gmp,256,10,1035,9,19315.38,19115.66,19.95
+tochars,gmpz,256,10,989,9,20305.99,19801.74,20.14
+tochars,auto,2000,10,30,9,673793.07,665931.93,20.24
+tochars,kernel,2000,10,30,9,667880.57,653176.40,19.98
+tochars,gmp,2000,10,49,9,408795.06,402588.43,19.99
+tochars,gmpz,2000,10,49,9,416157.31,413322.27,20.42
+tochars,auto,16384,10,2,3,18844458.00,18597666.50,37.53
+tochars,kernel,16384,10,2,3,18536229.50,18296021.00,37.00
+tochars,gmp,16384,10,3,3,7869361.00,7858638.67,23.67
+tochars,gmpz,16384,10,3,3,7960333.33,7951000.00,23.99
+tochars,auto,131072,10,1,3,383542542.00,383145750.00,384.45
+tochars,kernel,131072,10,1,3,381224750.00,380424625.00,382.29
+tochars,gmp,131072,10,1,3,111422500.00,111355500.00,111.49
+tochars,gmpz,131072,10,1,3,112258250.00,111882792.00,112.92
+tochars,auto,262144,10,1,3,971435916.00,956870709.00,971.43
+tochars,kernel,262144,10,1,3,963486292.00,961218750.00,962.93
+tochars,gmp,262144,10,1,3,268464750.00,266419792.00,267.98
+tochars,gmpz,262144,10,1,3,272622958.00,271423083.00,273.00
+tochars,auto,16,16,230499,9,86.44,84.02,19.97
+tochars,gmp,16,16,247750,9,82.76,80.47,20.42
+tochars,gmpz,16,16,121686,9,168.72,164.48,20.45
+tochars,auto,2000,16,1957,9,9678.10,9603.71,19.13
+tochars,gmp,2000,16,2194,9,9108.55,8783.92,20.02
+tochars,gmpz,2000,16,1168,9,16569.60,16254.67,19.39
+tochars,auto,131072,16,33,3,617247.48,616933.09,20.39
+tochars,gmp,131072,16,35,3,586502.37,577755.94,20.50
+tochars,gmpz,131072,16,18,3,1101824.06,1088405.11,19.76
+fromchars,auto,4,10,216849,9,91.58,89.98,19.85
+fromchars,kernel,4,10,622670,9,32.34,31.94,20.27
+fromchars,gmp,4,10,613933,9,31.11,30.08,18.99
+fromchars,gmpz,4,10,261144,9,76.54,73.87,19.89
+fromchars,auto,16,10,52886,9,377.81,370.36,20.00
+fromchars,kernel,16,10,105027,9,189.97,184.15,19.86
+fromchars,gmp,16,10,135737,9,144.78,142.33,19.55
+fromchars,gmpz,16,10,62317,9,320.59,310.12,19.94
+fromchars,auto,64,10,9225,9,2165.75,2123.73,19.92
+fromchars,kernel,64,10,12693,9,1554.93,1524.01,19.69
+fromchars,gmp,64,10,20742,9,964.50,952.99,20.09
+fromchars,gmpz,64,10,12302,9,1622.38,1605.27,19.98
+fromchars,auto,256,10,1453,9,13699.85,13603.58,19.94
+fromchars,kernel,256,10,1727,9,11459.88,11376.04,19.92
+fromchars,gmp,256,10,2470,9,8190.76,8082.79,20.21
+fromchars,gmpz,256,10,1874,9,10589.98,10424.98,19.86
+fromchars,auto,2000,10,67,9,305182.84,303179.72,20.52
+fromchars,kernel,2000,10,70,9,287724.40,282435.11,20.10
+fromchars,gmp,2000,10,109,9,184844.80,183551.23,20.17
+fromchars,gmpz,2000,10,97,9,206904.64,202057.99,20.08
+fromchars,auto,16384,10,3,3,9426278.00,9316486.00,28.21
+fromchars,kernel,16384,10,3,3,9284388.67,9264222.33,27.92
+fromchars,gmp,16384,10,6,3,3480597.33,3480451.50,20.95
+fromchars,gmpz,16384,10,6,3,3675743.00,3609007.00,21.97
+fromchars,auto,131072,10,1,3,162886333.00,161279500.00,162.99
+fromchars,kernel,131072,10,1,3,158380209.00,157605875.00,159.06
+fromchars,gmp,131072,10,1,3,47053875.00,46653792.00,47.00
+fromchars,gmpz,131072,10,1,3,47844333.00,47541042.00,48.05
+gcd,auto,2,2,59667,9,337.36,333.94,20.34
+gcd,kernel,2,2,63297,9,320.58,311.23,20.20
+gcd,gmp,2,2,197813,9,98.30,96.02,19.49
+gcd,gmpz,2,2,204324,9,101.36,99.59,20.64
+gcd,auto,4,4,28115,9,709.11,686.45,19.86
+gcd,kernel,4,4,31072,9,647.97,637.10,20.10
+gcd,gmp,4,4,41108,9,477.47,466.48,19.61
+gcd,gmpz,4,4,41654,9,473.62,458.38,19.63
+gcd,auto,16,16,6009,9,3133.94,3103.91,19.10
+gcd,kernel,16,16,6516,9,3065.49,3004.75,19.91
+gcd,gmp,16,16,8422,9,2371.16,2330.43,19.98
+gcd,gmpz,16,16,8333,9,2402.48,2340.93,20.00
+gcd,auto,64,64,1160,9,17822.20,17297.70,20.67
+gcd,kernel,64,64,1170,9,17050.18,16866.56,19.96
+gcd,gmp,64,64,1704,9,11771.32,11631.90,20.10
+gcd,gmpz,64,64,1685,9,12185.98,12066.27,20.74
+gcd,auto,256,256,121,9,160540.29,157028.24,19.37
+gcd,kernel,256,256,122,9,160368.85,156953.89,19.62
+gcd,gmp,256,256,236,9,80447.39,78524.01,19.02
+gcd,gmpz,256,256,245,9,80946.43,79308.67,19.92
+gcd,auto,1024,1024,10,9,2156420.80,2121650.00,21.71
+gcd,kernel,1024,1024,10,9,2207954.20,2189633.30,22.15
+gcd,gmp,1024,1024,28,9,713946.43,706095.25,19.95
+gcd,gmpz,1024,1024,30,9,723491.67,701644.47,21.80
+gcd,auto,2000,2000,3,9,7834847.33,7703639.00,23.55
+gcd,kernel,2000,2000,3,9,7860986.33,7654736.00,23.56
+gcd,gmp,2000,2000,10,9,2069741.70,2059820.80,20.72
+gcd,gmpz,2000,2000,10,9,2020495.80,1994050.00,20.20
+gcd,auto,16384,16384,1,3,488557292.00,487833667.00,488.34
+gcd,kernel,16384,16384,1,3,488269042.00,483842583.00,487.90
+gcd,gmp,16384,16384,1,3,40133667.00,40100250.00,40.29
+gcd,gmpz,16384,16384,1,3,39932667.00,39795208.00,39.95
+gcd,auto,65536,65536,1,1,7767268375.00,7767268375.00,7767.27
+gcd,kernel,65536,65536,1,1,7708156292.00,7708156292.00,7708.16
+gcd,gmp,65536,65536,1,1,237981500.00,237981500.00,237.98
+gcd,gmpz,65536,65536,1,1,237501542.00,237501542.00,237.50
diff --git a/tests/beman/big_int/perf/gmp_gap_x64_clang23.csv b/tests/beman/big_int/perf/gmp_gap_x64_clang23.csv
new file mode 100644
index 00000000..7aab7757
--- /dev/null
+++ b/tests/beman/big_int/perf/gmp_gap_x64_clang23.csv
@@ -0,0 +1,522 @@
+# gap_sweep date 2026-10-06T14:03:23Z host mborland-MS-7C75 Linux x86_64
+# gap_sweep bin builds/clang23-native/shape_sweep seed=1 ops=add,sub,shl,shr,cmp,mul,sqr,divrem,tochars,fromchars,gcd bands=small,medium,large pin=taskset -c 2
+#const square_long_cutoff=10 karatsuba_cutoff=260 karatsuba_fallback=230 square_karatsuba_cutoff=257 toom_cook_3_cutoff=1600 toom_cook_4_cutoff=4000 toom_cook_6_5_cutoff=4000 toom_cook_8_5_cutoff=6000 square_toom_cook_3_cutoff=4200 square_toom_cook_4_cutoff=6000 square_toom_cook_6_5_cutoff=9000 square_toom_cook_8_5_cutoff=14000 fft_mul_min_limbs=16000 fft_mul_model_num=15 fft_mul_model_den=64 square_fft_min_limbs=8000 square_fft_model_num=75 square_fft_model_den=512 fft_model_log_power=1 fft_model_root_degree=2 fft_cyclic_cutoff=8192 multiply_mod_bnm1_cutoff=16 mul_slice_z0_min=260 mul_slice_z0_num=7 mul_slice_z0_den=4 mul_slice_z1_min=1600 mul_slice_z1_num=3 mul_slice_z1_den=2 mul_slice_z2_min=30000 mul_slice_z2_num=4 mul_slice_z2_den=3 mul_slice_z3_min=0 mul_slice_z3_num=3 mul_slice_z3_den=2 mul_slice_z4_min=0 mul_slice_z4_num=3 mul_slice_z4_den=2 mul_slice_z5_min=0 mul_slice_z5_num=3 mul_slice_z5_den=2 mul_slice_zone_count=3 ifma_multiply_min_limbs=6 ifma_multiply_mid_limbs=8 ifma_multiply_high_limbs=13 ifma_multiply_always_limbs=20 ifma_multiply_min_product_low=900 ifma_multiply_min_product_mid=450 ifma_multiply_min_product_high=330 ifma_square_min_limbs=16 ifma_square_native_max_limbs=256 burnikel_ziegler_cutoff=160 burnikel_ziegler_offset=64 barrett_march_cutoff=512 barrett_march8_cutoff=4096 barrett_quarter_cutoff=49152 barrett_balanced_cutoff=196608 reciprocal_span_cutoff=512 fast_input_basecase_chunks=512 fast_output_basecase_chunks=16 fast_input_charconv_min_chunks=8 fast_output_preinv_min_limbs=98304 ARCH=x86_64 BMI2_ADX=1 AVX512_IFMA=1 SIMD_MUL=1 limb_bits=64 ndebug=1 optimize=1 isa_bmi2=1 isa_adx=1 isa_avx2=1 isa_avx512f=1 isa_avx512ifma=1 compiler=clang-23.0.0_(++20260621082240+441725611d0e-1~exp1~20260621082250.1677) via=cmake build_type=RelWithDebInfo cxx_flags=-march=native_-O3__-O2_-g_-DNDEBUG_defs:
+op,path,la,lb,reps,rounds,median_ns,min_ns,round_ms_actual
+add,auto,1,1,2474228,9,7.91,7.85,19.56
+add,inplace,1,1,2518524,9,3.96,3.94,20.02
+add,kernel,1,1,10587486,9,1.89,1.89,20.01
+add,gmp,1,1,8824180,9,2.27,2.27,20.00
+add,gmpz,1,1,4603745,9,4.16,4.16,19.14
+add,auto,2,2,1234800,9,16.51,16.46,20.45
+add,inplace,2,2,1835853,9,5.44,5.40,19.98
+add,kernel,2,2,8416680,9,2.38,2.34,20.04
+add,gmp,2,2,8824177,9,2.27,2.27,20.00
+add,gmpz,2,2,4603829,9,4.16,4.16,19.18
+add,auto,4,4,1207128,9,16.62,16.53,20.06
+add,inplace,4,4,1793755,9,5.60,5.56,20.20
+add,kernel,4,4,6589784,9,2.97,2.92,19.54
+add,gmp,4,4,8145128,9,2.46,2.46,20.00
+add,gmpz,4,4,4412077,9,4.34,4.34,19.19
+add,auto,8,8,1129723,9,17.77,17.73,20.09
+add,inplace,8,8,1322786,9,7.43,7.27,19.63
+add,kernel,8,8,4547052,9,4.40,4.36,20.04
+add,gmp,8,8,5569568,9,3.61,3.59,20.08
+add,gmpz,8,8,4235511,9,4.72,4.72,20.00
+add,auto,16,16,953025,9,21.15,21.05,20.16
+add,inplace,16,16,840279,9,12.02,11.80,20.19
+add,kernel,16,16,2314667,9,8.64,8.64,20.01
+add,gmp,16,16,3599077,9,5.56,5.56,20.01
+add,gmpz,16,16,3325316,9,6.05,6.04,20.12
+add,auto,64,64,492802,9,40.63,40.51,20.02
+add,inplace,64,64,239491,9,41.96,41.66,20.10
+add,kernel,64,64,490278,9,40.77,40.76,19.99
+add,gmp,64,64,1269075,9,15.76,15.72,19.98
+add,gmpz,64,64,1179448,9,16.96,16.92,19.99
+add,auto,256,256,138771,9,143.93,143.77,19.98
+add,inplace,256,256,59127,9,169.05,168.96,19.99
+add,kernel,256,256,119096,9,167.93,167.91,20.00
+add,gmp,256,256,254524,9,78.55,78.45,19.99
+add,gmpz,256,256,251565,9,79.43,79.34,19.98
+add,auto,1024,1024,40159,9,497.86,497.51,20.00
+add,inplace,1024,1024,14758,9,677.64,677.57,20.00
+add,kernel,1024,1024,29595,9,675.81,675.80,20.00
+add,gmp,1024,1024,51154,9,391.01,390.95,20.00
+add,gmpz,1024,1024,51265,9,390.12,390.11,20.00
+add,auto,2000,2000,18756,9,1066.20,1066.00,20.00
+add,inplace,2000,2000,7565,9,1321.96,1321.87,20.00
+add,kernel,2000,2000,15115,9,1323.26,1323.22,20.03
+add,gmp,2000,2000,41973,9,476.55,474.95,20.00
+add,gmpz,2000,2000,41503,9,482.28,480.57,19.99
+add,auto,1024,1,129326,9,154.68,154.24,20.01
+add,inplace,1024,1,14793,9,676.04,676.00,20.00
+add,kernel,1024,1,68374,9,292.48,292.45,20.00
+add,gmp,1024,1,85763,9,233.19,232.73,20.04
+add,gmpz,1024,1,85426,9,233.78,233.46,19.97
+add,auto,16384,16384,2140,3,9350.57,9349.48,20.01
+add,inplace,16384,16384,923,3,10840.77,10839.79,20.01
+add,kernel,16384,16384,1845,3,10846.04,10845.71,20.01
+add,gmp,16384,16384,2663,3,7511.86,7511.83,20.00
+add,gmpz,16384,16384,2906,3,6892.84,6883.49,20.02
+add,auto,131072,131072,238,3,84320.60,84302.93,20.07
+add,inplace,131072,131072,116,3,86737.38,86736.82,20.12
+add,kernel,131072,131072,231,3,86743.76,86743.43,20.04
+add,gmp,131072,131072,338,3,59216.15,59210.13,20.01
+add,gmpz,131072,131072,335,3,59765.38,59759.23,20.02
+add,auto,1048576,1048576,22,3,927906.64,925633.95,20.42
+add,inplace,1048576,1048576,14,3,729356.25,727062.14,20.42
+add,kernel,1048576,1048576,25,3,821056.36,819524.44,20.52
+add,gmp,1048576,1048576,30,3,687977.50,687209.93,20.64
+add,gmpz,1048576,1048576,30,3,687677.80,687343.07,20.63
+sub,auto,1,1,2402677,9,8.31,8.29,20.02
+sub,inplace,1,1,2654096,9,3.75,3.73,20.00
+sub,kernel,1,1,13169787,9,1.51,1.51,19.90
+sub,gmp,1,1,8817544,9,2.27,2.27,19.99
+sub,gmpz,1,1,4387727,9,4.34,4.34,19.08
+sub,auto,2,2,1255295,9,15.95,15.88,20.01
+sub,inplace,2,2,2218362,9,4.45,4.42,19.85
+sub,kernel,2,2,9106832,9,2.20,2.17,20.00
+sub,gmp,2,2,8824174,9,2.27,2.27,20.00
+sub,gmpz,2,2,4412043,9,4.34,4.34,19.25
+sub,auto,4,4,1259688,9,15.98,15.91,20.15
+sub,inplace,4,4,1850592,9,5.40,5.29,20.03
+sub,kernel,4,4,7489147,9,2.67,2.65,20.05
+sub,gmp,4,4,8145271,9,2.46,2.46,20.01
+sub,gmpz,4,4,4219314,9,4.53,4.53,19.16
+sub,auto,8,8,1166032,9,17.23,17.11,20.08
+sub,inplace,8,8,1369585,9,7.39,7.12,20.12
+sub,kernel,8,8,5272445,9,3.79,3.79,19.98
+sub,gmp,8,8,5460410,9,3.66,3.63,19.97
+sub,gmpz,8,8,4024684,9,4.94,4.91,19.86
+sub,auto,16,16,1006139,9,20.25,20.02,20.46
+sub,inplace,16,16,915395,9,10.50,10.41,19.36
+sub,kernel,16,16,2998388,9,6.67,6.65,20.00
+sub,gmp,16,16,3588049,9,5.56,5.56,19.97
+sub,gmpz,16,16,3258071,9,6.06,6.05,19.79
+sub,auto,64,64,523862,9,38.06,37.97,19.95
+sub,inplace,64,64,243845,9,40.67,39.95,19.84
+sub,kernel,64,64,792214,9,25.24,25.24,20.00
+sub,gmp,64,64,1240939,9,16.12,16.08,19.99
+sub,gmpz,64,64,1216662,9,16.44,16.40,20.00
+sub,auto,256,256,141703,9,141.06,141.00,19.99
+sub,inplace,256,256,59452,9,168.21,168.12,20.00
+sub,kernel,256,256,169761,9,117.81,117.81,20.00
+sub,gmp,256,256,254497,9,78.54,78.44,20.00
+sub,gmpz,256,256,250773,9,79.60,79.48,20.01
+sub,auto,1024,1024,40386,9,495.05,494.94,20.00
+sub,inplace,1024,1024,14793,9,676.04,675.94,20.00
+sub,kernel,1024,1024,41620,9,480.55,480.55,20.00
+sub,gmp,1024,1024,51047,9,390.46,390.44,19.93
+sub,gmpz,1024,1024,51464,9,388.28,388.22,19.99
+sub,auto,2000,2000,18555,9,1077.79,1076.04,20.01
+sub,inplace,2000,2000,7571,9,1320.80,1320.76,20.00
+sub,kernel,2000,2000,21204,9,943.26,943.23,20.01
+sub,gmp,2000,2000,41683,9,477.57,476.00,19.91
+sub,gmpz,2000,2000,41707,9,480.21,478.59,20.01
+sub,auto,1024,1,206694,9,96.77,96.75,20.00
+sub,inplace,1024,1,14787,9,676.35,676.27,20.01
+sub,kernel,1024,1,59022,9,338.87,338.84,20.00
+sub,gmp,1024,1,85532,9,233.55,233.26,19.97
+sub,gmpz,1024,1,85349,9,233.83,233.56,19.96
+sub,auto,16384,16384,2101,3,9523.70,9523.70,20.01
+sub,inplace,16384,16384,923,3,10840.78,10840.04,20.02
+sub,kernel,16384,16384,2585,3,7739.82,7739.48,20.08
+sub,gmp,16384,16384,2904,3,6889.04,6888.81,20.01
+sub,gmpz,16384,16384,2909,3,6878.39,6876.09,20.02
+sub,auto,131072,131072,238,3,84339.21,84320.29,20.07
+sub,inplace,131072,131072,116,3,86731.60,86729.25,20.12
+sub,kernel,131072,131072,323,3,62010.35,62010.35,20.03
+sub,gmp,131072,131072,335,3,59754.46,59749.05,20.02
+sub,gmpz,131072,131072,331,3,60433.69,60432.82,20.00
+sub,auto,1048576,1048576,22,3,939760.23,934867.86,20.67
+sub,inplace,1048576,1048576,14,3,724682.18,723712.54,20.35
+sub,kernel,1048576,1048576,29,3,691167.45,690715.59,20.06
+sub,gmp,1048576,1048576,29,3,690626.69,690279.10,20.10
+sub,gmpz,1048576,1048576,29,3,690724.90,689647.93,20.04
+shl,auto,1,13,1102383,9,18.18,18.06,20.04
+shl,inplace,1,13,2361695,9,4.18,4.14,19.76
+shl,kernel,1,13,9549192,9,2.08,2.08,19.84
+shl,kernelip,1,13,4327428,9,2.35,2.31,20.35
+shl,gmp,1,13,8824209,9,2.08,2.08,18.35
+shl,gmpz,1,13,5854539,9,3.40,3.40,19.90
+shl,auto,4,13,861206,9,23.16,22.95,19.99
+shl,inplace,4,13,1383970,9,7.29,7.17,20.17
+shl,kernel,4,13,5220455,9,3.78,3.78,19.83
+shl,kernelip,4,13,2671007,9,3.79,3.77,20.24
+shl,gmp,4,13,7562779,9,2.48,2.46,18.98
+shl,gmpz,4,13,5026310,9,3.98,3.97,19.98
+shl,auto,16,13,773865,9,25.69,25.58,19.90
+shl,inplace,16,13,1323330,9,7.60,7.45,20.11
+shl,kernel,16,13,5020576,9,3.94,3.91,19.81
+shl,kernelip,16,13,1569294,9,6.36,6.36,19.99
+shl,gmp,16,13,4775990,9,4.26,4.23,20.39
+shl,gmpz,16,13,3497840,9,5.75,5.71,20.15
+shl,auto,256,13,275306,9,72.73,72.68,20.02
+shl,inplace,256,13,347424,9,28.79,28.78,20.00
+shl,kernel,256,13,809900,9,24.79,24.66,20.07
+shl,kernelip,256,13,371153,9,26.96,26.94,20.02
+shl,gmp,256,13,422801,9,47.17,47.07,19.94
+shl,gmpz,256,13,408309,9,49.13,49.10,20.07
+shl,auto,2000,13,41062,9,486.99,485.13,20.00
+shl,inplace,2000,13,54314,9,184.11,184.03,20.01
+shl,kernel,2000,13,127619,9,155.24,154.94,19.84
+shl,kernelip,2000,13,51545,9,194.06,193.89,20.00
+shl,gmp,2000,13,59616,9,335.48,335.40,20.00
+shl,gmpz,2000,13,59295,9,337.33,337.29,20.01
+shl,auto,2000,77,32608,9,615.13,613.99,20.09
+shl,inplace,2000,77,33543,9,298.00,297.87,19.99
+shl,kernel,2000,77,68962,9,289.11,288.30,19.96
+shl,kernelip,2000,77,32866,9,304.38,304.28,20.01
+shl,gmp,2000,77,59094,9,337.34,337.34,19.94
+shl,gmpz,2000,77,59099,9,338.39,338.34,20.00
+shl,auto,131072,13,276,3,72591.51,72586.71,20.04
+shl,inplace,131072,13,671,3,14893.77,14893.30,19.99
+shl,kernel,131072,13,459,3,43585.45,43576.31,20.06
+shl,kernelip,131072,13,570,3,17559.01,17556.61,20.02
+shl,gmp,131072,13,589,3,33956.19,33954.55,20.00
+shl,gmpz,131072,13,588,3,33995.05,33984.77,19.99
+shl,auto,1048576,13,20,3,999575.25,998874.40,19.99
+shl,inplace,1048576,13,71,3,142207.46,142189.70,20.19
+shl,kernel,1048576,13,32,3,640156.25,639737.84,20.48
+shl,kernelip,1048576,13,65,3,154571.39,154526.21,20.10
+shl,gmp,1048576,13,53,3,378616.74,378504.42,20.09
+shl,gmpz,1048576,13,55,3,368323.82,368014.04,20.27
+shr,auto,1,13,2088119,9,9.47,9.36,19.79
+shr,inplace,1,13,2894427,9,3.45,3.42,19.94
+shr,kernel,1,13,10581616,9,1.89,1.89,20.00
+shr,gmp,1,13,10589136,9,2.08,2.08,22.00
+shr,gmpz,1,13,5882763,9,3.23,3.22,18.99
+shr,auto,4,13,1019369,9,19.70,19.52,20.03
+shr,inplace,4,13,1590999,9,6.36,6.28,20.18
+shr,kernel,4,13,5585727,9,3.61,3.58,20.15
+shr,gmp,4,13,8657446,9,2.49,2.49,21.59
+shr,gmpz,4,13,4969535,9,3.99,3.98,19.83
+shr,auto,16,13,742855,9,26.93,26.84,20.01
+shr,inplace,16,13,962161,9,10.43,10.38,20.09
+shr,kernel,16,13,1150982,9,17.38,17.38,20.01
+shr,gmp,16,13,4845484,9,4.17,4.13,20.21
+shr,gmpz,16,13,3462012,9,5.78,5.75,20.02
+shr,auto,256,13,307250,9,65.05,64.88,19.99
+shr,inplace,256,13,384650,9,26.02,26.01,20.03
+shr,kernel,256,13,411037,9,47.61,47.44,19.57
+shr,gmp,256,13,424186,9,47.23,47.22,20.03
+shr,gmpz,256,13,396632,9,50.40,50.34,19.99
+shr,auto,2000,13,50484,9,397.13,396.39,20.06
+shr,inplace,2000,13,53748,9,185.89,185.86,19.99
+shr,kernel,2000,13,58558,9,341.19,340.78,19.98
+shr,gmp,2000,13,59614,9,335.47,335.45,20.01
+shr,gmpz,2000,13,59145,9,337.86,337.77,19.99
+shr,auto,2000,77,52245,9,382.16,381.42,19.97
+shr,inplace,2000,77,39689,9,251.98,251.96,20.00
+shr,kernel,2000,77,55162,9,362.56,362.53,20.00
+shr,gmp,2000,77,59564,9,335.93,335.90,20.01
+shr,gmpz,2000,77,59003,9,338.88,338.79,20.00
+shr,auto,131072,13,280,3,71674.42,71664.23,20.07
+shr,inplace,131072,13,633,3,15806.61,15805.20,20.01
+shr,kernel,131072,13,436,3,45933.70,45917.87,20.03
+shr,gmp,131072,13,536,3,37345.54,37320.52,20.01
+shr,gmpz,131072,13,534,3,37421.98,37391.05,19.98
+shr,auto,1048576,13,20,3,1013635.85,1013094.15,20.27
+shr,inplace,1048576,13,68,3,147936.31,147920.25,20.12
+shr,kernel,1048576,13,31,3,663570.45,663342.23,20.57
+shr,gmp,1048576,13,51,3,386093.67,385120.41,19.68
+shr,gmpz,1048576,13,52,3,386914.63,386733.21,20.12
+cmp,auto,1,1,9625532,9,2.08,2.08,20.00
+cmp,kernel,1,1,13236117,9,1.51,1.51,20.00
+cmp,gmp,1,1,10587187,9,2.08,2.08,22.00
+cmp,gmpz,1,1,8139273,9,2.46,2.46,20.02
+cmp,auto,4,4,7387729,9,2.69,2.65,19.80
+cmp,kernel,4,4,9138428,9,2.17,2.15,20.04
+cmp,gmp,4,4,5573268,9,3.78,3.78,21.06
+cmp,gmpz,4,4,4603919,9,4.34,4.34,20.01
+cmp,auto,16,16,3449855,9,5.80,5.80,20.03
+cmp,kernel,16,16,2786599,9,7.18,7.18,20.00
+cmp,gmp,16,16,3917855,9,5.29,5.29,20.81
+cmp,gmpz,16,16,2345517,9,8.80,8.50,20.52
+cmp,auto,256,256,217367,9,91.37,90.92,19.83
+cmp,kernel,256,256,225869,9,88.46,88.26,19.99
+cmp,gmp,256,256,334642,9,58.24,58.10,19.53
+cmp,gmpz,256,256,229266,9,87.24,87.15,20.01
+cmp,auto,2000,2000,31361,9,637.40,635.77,19.99
+cmp,kernel,2000,2000,31675,9,631.50,631.20,20.01
+cmp,gmp,2000,2000,48250,9,403.58,402.95,19.48
+cmp,gmpz,2000,2000,31661,9,631.66,631.43,20.00
+cmp,auto,131072,131072,453,3,44232.72,44227.91,20.04
+cmp,kernel,131072,131072,454,3,44118.49,44117.78,20.08
+cmp,gmp,131072,131072,500,3,40031.72,40018.07,20.02
+cmp,gmpz,131072,131072,357,3,56160.67,56160.20,20.07
+mul,auto,1,1,1402118,9,14.33,14.26,20.10
+mul,gmp,1,1,5843602,9,3.42,3.41,20.00
+mul,gmpz,1,1,5042261,9,4.16,4.16,20.95
+mul,auto,2,2,771766,9,25.93,25.90,20.02
+mul,kernel,2,2,3108800,9,6.45,6.43,20.05
+mul,gmp,2,2,5294932,9,3.77,3.75,19.98
+mul,gmpz,2,2,3292604,9,6.14,6.09,20.21
+mul,auto,3,3,731242,9,27.40,27.29,20.02
+mul,kernel,3,3,2568245,9,7.78,7.76,19.99
+mul,gmp,3,3,2599699,9,7.70,7.67,20.03
+mul,gmpz,3,3,2055020,9,9.90,9.88,20.36
+mul,auto,4,4,681174,9,29.37,29.28,20.03
+mul,kernel,4,4,2065760,9,9.70,9.67,20.04
+mul,gmp,4,4,2017371,9,10.08,9.96,20.32
+mul,gmpz,4,4,1693467,9,11.91,11.89,20.19
+mul,auto,6,6,497397,9,40.21,39.91,19.99
+mul,kernel,6,6,1271354,9,15.68,15.65,19.93
+mul,gmp,6,6,1281462,9,15.65,15.61,20.06
+mul,gmpz,6,6,1162893,9,17.20,17.13,19.99
+mul,auto,8,8,452857,9,44.08,43.68,19.99
+mul,kernel,8,8,862067,9,23.15,23.13,19.97
+mul,gmp,8,8,733994,9,27.19,27.04,19.95
+mul,gmpz,8,8,688778,9,28.96,28.54,19.92
+mul,auto,12,12,297927,9,67.04,66.88,19.98
+mul,kernel,12,12,441499,9,45.29,45.27,20.00
+mul,gmp,12,12,410152,9,48.81,48.74,20.07
+mul,gmpz,12,12,398133,9,50.24,50.22,20.02
+mul,auto,16,16,201817,9,99.00,98.85,19.97
+mul,kernel,16,16,261003,9,76.62,76.54,20.00
+mul,gmp,16,16,243454,9,82.16,82.11,20.01
+mul,gmpz,16,16,240106,9,83.27,83.23,20.00
+mul,auto,32,32,95350,9,209.47,209.21,20.03
+mul,kernel,32,32,110257,9,182.14,181.69,20.08
+mul,gmp,32,32,74346,9,269.03,268.93,20.00
+mul,gmpz,32,32,73485,9,272.02,271.89,19.99
+mul,auto,64,64,38932,9,511.65,510.73,19.92
+mul,kernel,64,64,41590,9,479.12,478.56,19.93
+mul,gmp,64,64,23208,9,859.22,858.67,19.94
+mul,gmpz,64,64,23238,9,860.75,860.26,20.00
+mul,auto,128,128,11544,9,1732.05,1723.49,19.98
+mul,kernel,128,128,11845,9,1686.48,1685.41,20.00
+mul,gmp,128,128,7139,9,2801.84,2800.09,20.00
+mul,gmpz,128,128,7394,9,2705.12,2704.66,20.04
+mul,auto,256,256,3191,9,6259.01,6253.80,19.98
+mul,kernel,256,256,3223,9,6196.89,6191.23,19.98
+mul,gmp,256,256,2579,9,7754.90,7749.59,20.00
+mul,gmpz,256,256,2571,9,7779.48,7774.44,20.00
+mul,auto,512,512,1047,9,19076.93,19042.59,19.97
+mul,kernel,512,512,1052,9,18975.85,18951.83,19.98
+mul,gmp,512,512,959,9,20842.47,20833.21,19.99
+mul,gmpz,512,512,959,9,20862.77,20854.17,20.01
+mul,auto,1000,1000,339,9,59192.24,59072.42,20.07
+mul,kernel,1000,1000,340,9,58831.32,58772.39,20.01
+mul,gmp,1000,1000,376,9,53314.60,53287.37,20.05
+mul,gmpz,1000,1000,376,9,53257.71,53221.80,20.03
+mul,auto,2000,2000,115,9,173753.83,173534.46,19.98
+mul,kernel,2000,2000,116,9,173314.23,173242.92,20.11
+mul,gmp,2000,2000,139,9,144013.05,143937.32,20.02
+mul,gmpz,2000,2000,139,9,143983.14,143910.40,20.01
+mul,auto,2000,100,1117,9,17919.38,17909.67,20.05
+mul,kernel,2000,100,1129,9,17730.04,17720.05,20.02
+mul,gmp,2000,100,560,9,35734.71,35683.21,20.01
+mul,gmpz,2000,100,561,9,35699.82,35658.01,20.03
+mul,auto,4096,4096,40,3,512897.47,512811.15,20.52
+mul,kernel,4096,4096,40,3,511803.03,510842.60,20.46
+mul,gmp,4096,4096,53,3,383551.21,383400.62,20.33
+mul,gmpz,4096,4096,53,3,383540.34,383336.06,20.33
+mul,auto,16384,16384,6,3,3765633.33,3763553.17,22.67
+mul,kernel,16384,16384,6,3,3760256.67,3756076.17,22.57
+mul,gmp,16384,16384,12,3,1764386.50,1762292.75,21.17
+mul,gmpz,16384,16384,12,3,1763784.58,1763190.67,21.17
+mul,auto,65536,65536,1,3,24316130.00,24307817.00,24.32
+mul,kernel,65536,65536,1,3,24226416.00,24212386.00,24.22
+mul,gmp,65536,65536,3,3,8624699.33,8619760.67,25.87
+mul,gmpz,65536,65536,3,3,8612307.67,8608688.00,25.84
+mul,auto,262144,262144,1,3,105573476.00,105569466.00,105.59
+mul,kernel,262144,262144,1,3,103952279.00,103932364.00,104.05
+mul,gmp,262144,262144,1,3,49631691.00,49620431.00,49.64
+mul,gmpz,262144,262144,1,3,49610465.00,49591436.00,49.61
+mul,auto,1048576,1048576,1,3,623753637.00,622284610.00,626.33
+mul,kernel,1048576,1048576,1,3,630091197.00,620113948.00,628.34
+mul,gmp,1048576,1048576,1,3,246897358.00,246850496.00,247.12
+mul,gmpz,1048576,1048576,1,3,246593089.00,246330517.00,246.59
+mul,auto,262144,8192,1,3,43780798.00,43719723.00,43.79
+mul,kernel,262144,8192,1,3,57887152.00,57757812.00,57.85
+mul,gmp,262144,8192,2,3,19087220.00,19082164.00,38.18
+mul,gmpz,262144,8192,2,3,19494965.00,19361615.50,38.92
+sqr,auto,4,4,658017,9,30.56,30.28,20.07
+sqr,kernel,4,4,2311565,9,8.64,8.62,19.98
+sqr,gmp,4,4,3434066,9,5.77,5.73,19.80
+sqr,gmpz,4,4,2285500,9,8.78,8.76,20.07
+sqr,auto,16,16,262466,9,76.29,76.22,20.07
+sqr,kernel,16,16,335154,9,59.37,59.34,19.90
+sqr,gmp,16,16,413097,9,48.42,48.39,20.01
+sqr,gmpz,16,16,388242,9,51.71,51.70,20.10
+sqr,auto,64,64,69020,9,289.88,289.65,20.01
+sqr,kernel,64,64,72774,9,274.73,274.69,19.99
+sqr,gmp,64,64,36457,9,548.56,548.34,20.02
+sqr,gmpz,64,64,36368,9,550.38,549.95,20.02
+sqr,auto,512,512,1909,9,10488.57,10477.72,20.04
+sqr,kernel,512,512,1927,9,10392.49,10386.02,20.02
+sqr,gmp,512,512,1325,9,15092.98,15083.89,20.00
+sqr,gmpz,512,512,1326,9,15092.35,15082.94,20.01
+sqr,auto,2000,2000,202,9,99439.32,99403.25,20.10
+sqr,kernel,2000,2000,203,9,98773.79,98689.44,20.05
+sqr,gmp,2000,2000,199,9,100804.34,100774.58,20.06
+sqr,gmpz,2000,2000,199,9,100817.29,100769.07,20.06
+sqr,auto,16384,16384,9,3,2391107.44,2390644.89,21.53
+sqr,kernel,16384,16384,9,3,2374508.67,2370265.22,21.41
+sqr,gmp,16384,16384,18,3,1145607.78,1145462.78,20.62
+sqr,gmpz,16384,16384,18,3,1145680.28,1145621.72,20.62
+sqr,auto,262144,262144,1,3,111231408.00,111185939.00,111.23
+sqr,kernel,262144,262144,1,3,110865270.00,110811207.00,110.86
+sqr,gmp,262144,262144,1,3,32815355.00,32778730.00,32.80
+sqr,gmpz,262144,262144,1,3,32807791.00,32802007.00,32.84
+sqr,auto,1048576,1048576,1,3,467980492.00,467320851.00,467.92
+sqr,kernel,1048576,1048576,1,3,468002808.00,466120521.00,472.33
+sqr,gmp,1048576,1048576,1,3,165073339.00,164964432.00,165.08
+sqr,gmpz,1048576,1048576,1,3,165199952.00,165134166.00,165.29
+divrem,auto,2,1,610887,9,31.48,31.30,19.41
+divrem,kernel,2,1,3647820,9,5.49,5.49,20.04
+divrem,gmp,2,1,2382272,9,8.37,8.33,19.94
+divrem,gmpz,2,1,1487311,9,13.45,13.45,20.01
+divrem,auto,8,4,199870,9,99.95,91.50,19.61
+divrem,kernel,8,4,430873,9,46.45,46.40,20.02
+divrem,gmp,8,4,518723,9,38.70,38.41,20.07
+divrem,gmpz,8,4,479711,9,41.78,41.58,20.04
+divrem,auto,32,16,59816,9,334.30,333.71,19.99
+divrem,kernel,32,16,70739,9,282.89,282.84,20.02
+divrem,gmp,32,16,104518,9,191.41,190.66,20.00
+divrem,gmpz,32,16,102302,9,195.55,195.18,20.01
+divrem,auto,128,64,6116,9,3267.80,3264.59,19.99
+divrem,kernel,128,64,6304,9,3175.36,3172.65,20.03
+divrem,gmp,128,64,11612,9,1721.43,1720.58,19.99
+divrem,gmpz,128,64,11601,9,1724.67,1723.48,20.01
+divrem,auto,512,256,699,9,28652.33,28643.83,20.03
+divrem,kernel,512,256,704,9,28439.68,28428.16,20.03
+divrem,gmp,512,256,1215,9,16222.43,16220.10,19.72
+divrem,gmpz,512,256,1231,9,16257.86,16252.13,20.02
+divrem,auto,2000,1000,113,9,177212.75,177119.09,20.03
+divrem,kernel,2000,1000,113,9,177626.27,177459.23,20.08
+divrem,gmp,2000,1000,144,9,139361.81,139307.35,20.07
+divrem,gmpz,2000,1000,144,9,139278.24,139189.51,20.09
+divrem,auto,4000,2000,44,9,463662.70,463312.11,20.43
+divrem,kernel,4000,2000,44,9,460822.32,460495.20,20.29
+divrem,gmp,4000,2000,61,9,333197.75,333074.08,20.33
+divrem,gmpz,4000,2000,60,9,333652.13,333581.48,20.02
+divrem,auto,2000,100,140,9,143627.59,143591.21,20.11
+divrem,kernel,2000,100,141,9,142436.03,142318.84,20.08
+divrem,gmp,2000,100,276,9,72614.60,72590.62,20.04
+divrem,gmpz,2000,100,276,9,72562.53,72531.95,20.03
+divrem,auto,65536,1,86,9,233033.09,232111.26,20.03
+divrem,kernel,65536,1,92,9,219860.52,211986.07,20.09
+divrem,gmp,65536,1,136,9,148091.21,148078.02,20.17
+divrem,gmpz,65536,1,136,9,148149.46,148114.60,20.16
+divrem,auto,16384,8192,6,3,3864721.33,3856641.00,23.21
+divrem,kernel,16384,8192,6,3,3790203.00,3789512.00,22.74
+divrem,gmp,16384,8192,10,3,2119627.70,2119515.90,21.20
+divrem,gmpz,16384,8192,10,3,2120011.20,2118092.50,21.19
+divrem,auto,131072,65536,1,3,76836920.00,76821682.00,76.84
+divrem,kernel,131072,65536,1,3,76610934.00,76597691.00,76.75
+divrem,gmp,131072,65536,1,3,24287323.00,24280080.00,24.29
+divrem,gmpz,131072,65536,1,3,24272717.00,24268118.00,24.27
+divrem,auto,1048576,524288,1,3,1044445063.00,1042843292.00,1046.03
+divrem,kernel,1048576,524288,1,3,1043917390.00,1043631639.00,1044.02
+divrem,gmp,1048576,524288,1,3,293016485.00,292786401.00,292.96
+divrem,gmpz,1048576,524288,1,3,292634702.00,292607048.00,292.75
+tochars,auto,1,10,1433023,9,13.96,13.88,20.04
+tochars,kernel,1,10,369958,9,54.19,53.92,20.05
+tochars,gmp,1,10,1001728,9,19.64,19.55,19.69
+tochars,gmpz,1,10,598225,9,33.47,33.38,20.03
+tochars,auto,4,10,171208,9,116.80,116.59,20.00
+tochars,kernel,4,10,123706,9,161.97,161.73,20.05
+tochars,gmp,4,10,292396,9,68.33,68.22,20.02
+tochars,gmpz,4,10,210191,9,95.14,94.99,20.02
+tochars,auto,16,10,16265,9,1229.48,1228.92,20.00
+tochars,kernel,16,10,18697,9,1068.11,1067.39,19.98
+tochars,gmp,16,10,39827,9,502.41,502.23,20.01
+tochars,gmpz,16,10,33807,9,592.34,589.36,20.01
+tochars,auto,64,10,3559,9,5608.86,5604.74,19.97
+tochars,kernel,64,10,3812,9,5248.15,5243.05,20.01
+tochars,gmp,64,10,7487,9,2672.59,2668.77,20.00
+tochars,gmpz,64,10,6729,9,2965.02,2959.95,19.95
+tochars,auto,256,10,568,9,35242.29,35235.08,20.03
+tochars,kernel,256,10,587,9,34092.33,34080.73,20.01
+tochars,gmp,256,10,1077,9,18607.23,18582.34,20.04
+tochars,gmpz,256,10,1016,9,19708.10,19683.73,20.02
+tochars,auto,2000,10,34,9,601015.94,600676.65,20.44
+tochars,kernel,2000,10,35,9,577118.51,577004.69,20.20
+tochars,gmp,2000,10,50,9,401411.00,400986.94,20.10
+tochars,gmpz,2000,10,49,9,410486.80,410236.57,20.12
+tochars,auto,16384,10,2,3,12482140.50,12480658.00,24.96
+tochars,kernel,16384,10,2,3,12427873.50,12424897.00,24.86
+tochars,gmp,16384,10,3,3,8360899.67,8360491.00,25.09
+tochars,gmpz,16384,10,3,3,8425301.33,8424601.33,25.28
+tochars,auto,131072,10,1,3,267709336.00,267359246.00,267.63
+tochars,kernel,131072,10,1,3,266591426.00,266546193.00,266.58
+tochars,gmp,131072,10,1,3,122165535.00,122130929.00,122.16
+tochars,gmpz,131072,10,1,3,124394486.00,122694859.00,124.09
+tochars,auto,262144,10,1,3,743187590.00,742611683.00,743.09
+tochars,kernel,262144,10,1,3,738877488.00,738741070.00,738.87
+tochars,gmp,262144,10,1,3,289037127.00,288974293.00,289.42
+tochars,gmpz,262144,10,1,3,290411661.00,290138652.00,292.61
+tochars,auto,16,16,177645,9,113.50,112.10,20.15
+tochars,gmp,16,16,202328,9,98.90,98.70,20.01
+tochars,gmpz,16,16,123422,9,162.28,161.15,20.03
+tochars,auto,2000,16,1451,9,14080.70,13631.26,20.48
+tochars,gmp,2000,16,1800,9,11111.27,11109.56,20.01
+tochars,gmpz,2000,16,1116,9,17922.53,17920.86,20.01
+tochars,auto,131072,16,17,3,1204419.76,1202254.47,20.47
+tochars,gmp,131072,16,27,3,744458.04,744444.15,20.10
+tochars,gmpz,131072,16,17,3,1173573.06,1173339.65,19.95
+fromchars,auto,4,10,115451,9,173.49,173.03,20.06
+fromchars,kernel,4,10,597122,9,33.46,33.38,19.97
+fromchars,gmp,4,10,636438,9,31.35,31.15,19.95
+fromchars,gmpz,4,10,300990,9,66.10,65.98,19.89
+fromchars,auto,16,10,56751,9,351.37,351.01,19.94
+fromchars,kernel,16,10,137317,9,145.62,145.56,20.01
+fromchars,gmp,16,10,160764,9,124.58,124.48,20.04
+fromchars,gmpz,16,10,77779,9,255.23,254.68,19.87
+fromchars,auto,64,10,11071,9,1806.73,1804.29,20.01
+fromchars,kernel,64,10,18112,9,1105.28,1103.81,20.02
+fromchars,gmp,64,10,22585,9,883.91,882.26,19.96
+fromchars,gmpz,64,10,14557,9,1374.86,1373.24,20.03
+fromchars,auto,256,10,1260,9,15884.32,15872.50,20.01
+fromchars,kernel,256,10,1503,9,13331.24,13324.65,20.04
+fromchars,gmp,256,10,2488,9,8020.16,8015.48,19.96
+fromchars,gmpz,256,10,2015,9,9934.08,9923.33,20.05
+fromchars,auto,2000,10,67,9,302854.27,302769.45,20.31
+fromchars,kernel,2000,10,72,9,281380.10,281340.40,20.27
+fromchars,gmp,2000,10,110,9,182659.35,182583.74,20.11
+fromchars,gmpz,2000,10,102,9,197460.74,197429.21,20.14
+fromchars,auto,16384,10,4,3,6454926.50,6450536.00,25.84
+fromchars,kernel,16384,10,4,3,6300981.75,6299570.50,25.33
+fromchars,gmp,16384,10,6,3,3711306.33,3710098.17,22.27
+fromchars,gmpz,16384,10,6,3,3847762.83,3840649.67,23.09
+fromchars,auto,131072,10,1,3,129701222.00,129698005.00,129.70
+fromchars,kernel,131072,10,1,3,128024824.00,127768629.00,128.02
+fromchars,gmp,131072,10,1,3,50129350.00,50108569.00,50.19
+fromchars,gmpz,131072,10,1,3,52040304.00,51076615.00,51.83
+gcd,auto,2,2,46817,9,408.07,401.80,19.14
+gcd,kernel,2,2,52792,9,377.97,374.53,20.02
+gcd,gmp,2,2,156086,9,127.76,127.40,19.94
+gcd,gmpz,2,2,147753,9,135.78,134.18,20.16
+gcd,auto,4,4,25460,9,757.45,754.17,19.47
+gcd,kernel,4,4,28916,9,689.32,684.97,19.94
+gcd,gmp,4,4,37758,9,528.90,528.01,19.99
+gcd,gmpz,4,4,37173,9,537.98,537.50,20.00
+gcd,auto,16,16,6292,9,3166.56,3153.40,19.94
+gcd,kernel,16,16,6506,9,3064.33,3048.64,19.93
+gcd,gmp,16,16,7762,9,2578.13,2574.86,20.02
+gcd,gmpz,16,16,7686,9,2603.26,2601.08,20.01
+gcd,auto,64,64,1100,9,17925.62,17751.18,19.73
+gcd,kernel,64,64,1114,9,17764.34,17699.57,19.78
+gcd,gmp,64,64,1574,9,12705.97,12704.06,20.00
+gcd,gmpz,64,64,1566,9,12764.98,12760.37,20.00
+gcd,auto,256,256,116,9,173524.48,173085.75,20.14
+gcd,kernel,256,256,122,9,165343.32,165024.71,20.19
+gcd,gmp,256,256,217,9,92475.36,92079.78,20.07
+gcd,gmpz,256,256,217,9,92692.82,92444.29,20.13
+gcd,auto,1024,1024,13,9,1658517.62,1657835.69,21.56
+gcd,kernel,1024,1024,13,9,1660148.23,1658024.08,21.59
+gcd,gmp,1024,1024,24,9,861928.79,860330.08,20.69
+gcd,gmpz,1024,1024,23,9,895057.96,893365.52,20.58
+gcd,auto,2000,2000,4,9,5166533.50,5163501.00,20.66
+gcd,kernel,2000,2000,4,9,5165454.25,5161346.25,20.66
+gcd,gmp,2000,2000,9,9,2304201.89,2302832.11,20.74
+gcd,gmpz,2000,2000,9,9,2307668.44,2305385.89,20.78
+gcd,auto,16384,16384,1,3,332603908.00,332351172.00,332.77
+gcd,kernel,16384,16384,1,3,332705907.00,332605149.00,332.85
+gcd,gmp,16384,16384,1,3,44053699.00,44052206.00,44.06
+gcd,gmpz,16384,16384,1,3,44038371.00,44031267.00,44.04
+gcd,auto,65536,65536,1,1,5279977177.00,5279977177.00,5279.98
+gcd,kernel,65536,65536,1,1,5254968229.00,5254968229.00,5254.97
+gcd,gmp,65536,65536,1,1,262145446.00,262145446.00,262.15
+gcd,gmpz,65536,65536,1,1,261399461.00,261399461.00,261.40
diff --git a/tests/beman/big_int/perf/gmp_gap_x64_gcc13_linear.csv b/tests/beman/big_int/perf/gmp_gap_x64_gcc13_linear.csv
new file mode 100644
index 00000000..20b218ab
--- /dev/null
+++ b/tests/beman/big_int/perf/gmp_gap_x64_gcc13_linear.csv
@@ -0,0 +1,246 @@
+# gap_sweep date 2026-10-06T14:07:26Z host mborland-MS-7C75 Linux x86_64
+# gap_sweep bin builds/gcc13-native/shape_sweep seed=1 ops=add,sub,shl,shr,cmp bands=small,medium,large pin=taskset -c 2
+#const square_long_cutoff=10 karatsuba_cutoff=260 karatsuba_fallback=230 square_karatsuba_cutoff=257 toom_cook_3_cutoff=1600 toom_cook_4_cutoff=4000 toom_cook_6_5_cutoff=4000 toom_cook_8_5_cutoff=6000 square_toom_cook_3_cutoff=4200 square_toom_cook_4_cutoff=6000 square_toom_cook_6_5_cutoff=9000 square_toom_cook_8_5_cutoff=14000 fft_mul_min_limbs=16000 fft_mul_model_num=15 fft_mul_model_den=64 square_fft_min_limbs=8000 square_fft_model_num=75 square_fft_model_den=512 fft_model_log_power=1 fft_model_root_degree=2 fft_cyclic_cutoff=8192 multiply_mod_bnm1_cutoff=16 mul_slice_z0_min=260 mul_slice_z0_num=7 mul_slice_z0_den=4 mul_slice_z1_min=1600 mul_slice_z1_num=3 mul_slice_z1_den=2 mul_slice_z2_min=30000 mul_slice_z2_num=4 mul_slice_z2_den=3 mul_slice_z3_min=0 mul_slice_z3_num=3 mul_slice_z3_den=2 mul_slice_z4_min=0 mul_slice_z4_num=3 mul_slice_z4_den=2 mul_slice_z5_min=0 mul_slice_z5_num=3 mul_slice_z5_den=2 mul_slice_zone_count=3 ifma_multiply_min_limbs=6 ifma_multiply_mid_limbs=8 ifma_multiply_high_limbs=13 ifma_multiply_always_limbs=20 ifma_multiply_min_product_low=900 ifma_multiply_min_product_mid=450 ifma_multiply_min_product_high=330 ifma_square_min_limbs=16 ifma_square_native_max_limbs=256 burnikel_ziegler_cutoff=160 burnikel_ziegler_offset=64 barrett_march_cutoff=512 barrett_march8_cutoff=4096 barrett_quarter_cutoff=49152 barrett_balanced_cutoff=196608 reciprocal_span_cutoff=512 fast_input_basecase_chunks=512 fast_output_basecase_chunks=16 fast_input_charconv_min_chunks=8 fast_output_preinv_min_limbs=98304 ARCH=x86_64 BMI2_ADX=1 AVX512_IFMA=1 SIMD_MUL=1 limb_bits=64 ndebug=1 optimize=1 isa_bmi2=1 isa_adx=1 isa_avx2=1 isa_avx512f=1 isa_avx512ifma=1 compiler=gcc-13.4.0 via=cmake build_type=RelWithDebInfo cxx_flags=-march=native_-O3__-O2_-g_-DNDEBUG_defs:
+op,path,la,lb,reps,rounds,median_ns,min_ns,round_ms_actual
+add,auto,1,1,1995251,9,10.14,10.09,20.23
+add,inplace,1,1,2693497,9,3.70,3.69,20.03
+add,kernel,1,1,9611410,9,2.08,2.08,19.98
+add,gmp,1,1,8816555,9,2.27,2.27,19.99
+add,gmpz,1,1,4811563,9,4.16,4.16,20.01
+add,auto,2,2,1084079,9,18.28,18.23,19.83
+add,inplace,2,2,1770676,9,5.65,5.64,20.00
+add,kernel,2,2,7802124,9,2.55,2.53,19.87
+add,gmp,2,2,8822166,9,2.27,2.27,20.00
+add,gmpz,2,2,4812959,9,4.16,4.16,20.00
+add,auto,4,4,1006309,9,19.94,19.69,20.03
+add,inplace,4,4,1772437,9,5.61,5.59,19.96
+add,kernel,4,4,5836591,9,3.43,3.43,20.07
+add,gmp,4,4,8009254,9,2.50,2.50,20.00
+add,gmpz,4,4,4603854,9,4.34,4.34,20.02
+add,auto,8,8,873363,9,22.78,22.26,19.91
+add,inplace,8,8,1251237,9,7.99,7.98,20.01
+add,kernel,8,8,3594912,9,5.56,5.56,20.01
+add,gmp,8,8,5424319,9,3.69,3.69,20.01
+add,gmpz,8,8,4084609,9,4.88,4.85,19.94
+add,auto,16,16,729143,9,27.58,27.28,20.06
+add,inplace,16,16,729248,9,13.70,13.68,19.98
+add,kernel,16,16,1711513,9,11.61,11.61,19.87
+add,gmp,16,16,3632744,9,5.49,5.49,19.96
+add,gmpz,16,16,3074174,9,6.53,6.50,20.07
+add,auto,64,64,299732,9,66.57,66.36,19.94
+add,inplace,64,64,197209,9,50.69,50.64,19.99
+add,kernel,64,64,415832,9,48.10,48.10,20.00
+add,gmp,64,64,1292138,9,15.48,15.45,20.02
+add,gmpz,64,64,1204858,9,16.60,16.57,20.01
+add,auto,256,256,84975,9,235.35,235.33,20.01
+add,inplace,256,256,50391,9,198.47,198.45,20.00
+add,kernel,256,256,103189,9,193.62,193.62,19.98
+add,gmp,256,256,255276,9,78.35,78.25,20.00
+add,gmpz,256,256,251569,9,79.52,79.43,20.00
+add,auto,1024,1024,23243,9,860.61,860.48,20.02
+add,inplace,1024,1024,12730,9,785.67,785.63,20.00
+add,kernel,1024,1024,25830,9,774.33,774.31,20.01
+add,gmp,1024,1024,51269,9,390.07,390.02,20.00
+add,gmpz,1024,1024,51164,9,390.90,390.88,20.00
+add,auto,2000,2000,11264,9,1775.80,1775.47,20.00
+add,inplace,2000,2000,6591,9,1517.35,1517.31,20.00
+add,kernel,2000,2000,13233,9,1511.47,1511.44,20.00
+add,gmp,2000,2000,43411,9,460.62,458.64,19.97
+add,gmpz,2000,2000,42777,9,467.51,465.56,19.98
+add,auto,1024,1,70620,9,283.29,282.43,20.04
+add,inplace,1024,1,12851,9,774.40,774.36,19.94
+add,kernel,1024,1,34643,9,577.31,577.31,20.00
+add,gmp,1024,1,85740,9,233.25,232.92,20.00
+add,gmpz,1024,1,85399,9,234.21,233.78,20.04
+add,auto,16384,16384,1290,3,15250.04,15248.01,19.67
+add,inplace,16384,16384,804,3,12428.14,12427.26,19.98
+add,kernel,16384,16384,1614,3,12397.65,12397.02,20.01
+add,gmp,16384,16384,2658,3,7523.56,7523.16,20.00
+add,gmpz,16384,16384,2892,3,6937.34,6919.12,20.08
+add,auto,131072,131072,154,3,130187.79,130185.62,20.05
+add,inplace,131072,131072,101,3,99423.39,99422.86,20.09
+add,kernel,131072,131072,202,3,99187.58,99186.44,20.06
+add,gmp,131072,131072,340,3,58854.53,58853.01,20.01
+add,gmpz,131072,131072,346,3,57925.73,57916.50,20.04
+add,auto,1048576,1048576,17,3,1189655.12,1187025.94,20.24
+add,inplace,1048576,1048576,13,3,829542.04,829487.77,21.57
+add,kernel,1048576,1048576,22,3,915348.86,915294.73,20.14
+add,gmp,1048576,1048576,29,3,698982.66,697098.31,20.27
+add,gmpz,1048576,1048576,29,3,705613.59,703510.93,20.45
+sub,auto,1,1,1832672,9,10.86,10.73,19.86
+sub,inplace,1,1,2660252,9,3.74,3.71,19.90
+sub,kernel,1,1,11765720,9,1.70,1.70,20.00
+sub,gmp,1,1,8824214,9,2.27,2.27,20.06
+sub,gmpz,1,1,4603820,9,4.34,4.34,20.03
+sub,auto,2,2,988492,9,20.35,20.09,20.05
+sub,inplace,2,2,2240643,9,4.48,4.45,20.09
+sub,kernel,2,2,8659563,9,2.28,2.28,19.84
+sub,gmp,2,2,8824138,9,2.27,2.27,20.02
+sub,gmpz,2,2,4604008,9,4.34,4.34,20.00
+sub,auto,4,4,968296,9,20.71,20.62,20.06
+sub,inplace,4,4,1758240,9,5.69,5.69,20.05
+sub,kernel,4,4,6526459,9,3.06,3.06,20.01
+sub,gmp,4,4,8040456,9,2.49,2.49,20.01
+sub,gmpz,4,4,4387599,9,4.55,4.53,20.04
+sub,auto,8,8,877062,9,22.67,22.59,19.91
+sub,inplace,8,8,1228686,9,8.23,7.94,20.46
+sub,kernel,8,8,3459296,9,5.80,5.61,20.35
+sub,gmp,8,8,5425586,9,3.68,3.68,19.98
+sub,gmpz,8,8,4020844,9,4.97,4.93,20.01
+sub,auto,16,16,703829,9,28.52,28.10,20.06
+sub,inplace,16,16,785884,9,12.71,12.68,19.98
+sub,kernel,16,16,1706792,9,11.73,11.72,20.01
+sub,gmp,16,16,3633590,9,5.50,5.50,20.00
+sub,gmpz,16,16,2948794,9,6.79,6.76,20.06
+sub,auto,64,64,291569,9,68.47,68.40,19.97
+sub,inplace,64,64,202636,9,49.35,49.31,20.00
+sub,kernel,64,64,416310,9,48.05,48.04,20.01
+sub,gmp,64,64,1291998,9,15.48,15.45,20.01
+sub,gmpz,64,64,1198538,9,16.69,16.65,19.99
+sub,auto,256,256,82468,9,242.43,242.39,19.99
+sub,inplace,256,256,51339,9,194.78,194.77,20.00
+sub,kernel,256,256,103315,9,193.58,193.57,20.00
+sub,gmp,256,256,255354,9,78.32,78.22,20.03
+sub,gmpz,256,256,250463,9,79.84,79.74,19.99
+sub,auto,1024,1024,23022,9,868.83,868.69,20.00
+sub,inplace,1024,1024,12766,9,783.51,783.47,20.05
+sub,kernel,1024,1024,25827,9,774.30,774.26,20.01
+sub,gmp,1024,1024,51313,9,389.77,389.74,20.00
+sub,gmpz,1024,1024,51147,9,391.04,391.00,20.00
+sub,auto,2000,2000,11274,9,1773.89,1773.22,20.00
+sub,inplace,2000,2000,6595,9,1516.07,1516.02,20.01
+sub,kernel,2000,2000,13227,9,1512.19,1512.13,20.01
+sub,gmp,2000,2000,41986,9,476.26,474.64,19.99
+sub,gmpz,2000,2000,41338,9,483.76,482.38,19.99
+sub,auto,1024,1,69545,9,287.85,287.45,20.06
+sub,inplace,1024,1,12912,9,774.49,774.46,20.00
+sub,kernel,1024,1,34663,9,577.00,576.99,20.00
+sub,gmp,1024,1,85312,9,234.31,233.93,19.99
+sub,gmpz,1024,1,85261,9,234.57,234.12,19.99
+sub,auto,16384,16384,1328,3,15070.45,15070.41,20.01
+sub,inplace,16384,16384,805,3,12428.08,12427.81,20.01
+sub,kernel,16384,16384,1614,3,12395.17,12394.93,20.01
+sub,gmp,16384,16384,2871,3,6966.39,6966.31,20.00
+sub,gmpz,16384,16384,2927,3,6834.12,6833.61,20.00
+sub,auto,131072,131072,155,3,129853.69,129842.28,20.13
+sub,inplace,131072,131072,101,3,99420.49,99419.75,20.08
+sub,kernel,131072,131072,202,3,99162.80,99162.39,20.03
+sub,gmp,131072,131072,339,3,59125.91,59103.04,20.07
+sub,gmpz,131072,131072,342,3,58574.87,58560.61,20.04
+sub,auto,1048576,1048576,17,3,1179793.94,1176510.82,20.05
+sub,inplace,1048576,1048576,13,3,826770.92,826710.19,21.50
+sub,kernel,1048576,1048576,22,3,915738.82,915077.59,20.14
+sub,gmp,1048576,1048576,29,3,703728.28,702619.21,20.43
+sub,gmpz,1048576,1048576,29,3,704061.76,703933.76,20.42
+shl,auto,1,13,1197734,9,16.71,16.62,20.03
+shl,inplace,1,13,2495493,9,4.01,3.98,19.97
+shl,kernel,1,13,9937188,9,1.98,1.93,19.64
+shl,kernelip,1,13,6009984,9,1.68,1.65,20.16
+shl,gmp,1,13,9626187,9,2.08,2.08,20.00
+shl,gmpz,1,13,6147112,9,3.26,3.26,20.06
+shl,auto,4,13,1087834,9,18.43,18.31,20.06
+shl,inplace,4,13,1950688,9,5.11,5.08,19.94
+shl,kernel,4,13,5265661,9,3.79,3.78,20.01
+shl,kernelip,4,13,3441182,9,2.92,2.89,20.15
+shl,gmp,4,13,7955500,9,2.50,2.50,19.91
+shl,gmpz,4,13,4932480,9,4.05,4.05,20.00
+shl,auto,16,13,784412,9,25.58,25.39,20.08
+shl,inplace,16,13,1034762,9,9.63,9.62,19.96
+shl,kernel,16,13,2274841,9,8.72,8.69,19.85
+shl,kernelip,16,13,1347224,9,7.40,7.36,19.96
+shl,gmp,16,13,4490304,9,4.31,4.29,19.42
+shl,gmpz,16,13,3458240,9,5.80,5.78,20.06
+shl,auto,256,13,145064,9,137.81,137.62,20.02
+shl,inplace,256,13,108502,9,92.24,92.08,20.02
+shl,kernel,256,13,181405,9,110.20,110.17,19.99
+shl,kernelip,256,13,103100,9,96.85,96.61,19.97
+shl,gmp,256,13,419530,9,47.78,47.51,20.05
+shl,gmpz,256,13,400225,9,49.83,49.74,19.96
+shl,auto,2000,13,21760,9,918.77,918.45,19.99
+shl,inplace,2000,13,15463,9,646.19,646.12,19.99
+shl,kernel,2000,13,29017,9,688.96,688.50,19.99
+shl,kernelip,2000,13,14274,9,700.62,700.47,20.01
+shl,gmp,2000,13,59525,9,335.85,335.47,20.03
+shl,gmpz,2000,13,59181,9,338.12,337.92,20.06
+shl,auto,2000,77,18842,9,1062.85,1061.29,20.03
+shl,inplace,2000,77,13245,9,754.66,754.47,19.99
+shl,kernel,2000,77,24331,9,822.05,822.02,20.01
+shl,kernelip,2000,77,12347,9,809.97,809.95,20.02
+shl,gmp,2000,77,59252,9,337.73,337.72,20.02
+shl,gmpz,2000,77,59133,9,338.23,338.17,20.01
+shl,auto,131072,13,201,3,99459.07,99148.84,19.98
+shl,inplace,131072,13,232,3,43195.68,42875.17,20.00
+shl,kernel,131072,13,292,3,68506.54,68496.80,20.00
+shl,kernelip,131072,13,220,3,45502.37,45493.09,20.04
+shl,gmp,131072,13,590,3,33932.51,33918.18,20.02
+shl,gmpz,131072,13,590,3,33951.06,33920.83,20.06
+shl,auto,1048576,13,18,3,1137088.39,1136922.44,20.47
+shl,inplace,1048576,13,29,3,349580.53,349530.38,20.28
+shl,kernel,1048576,13,28,3,718996.79,718989.54,20.13
+shl,kernelip,1048576,13,28,3,364969.16,364821.73,20.44
+shl,gmp,1048576,13,53,3,376284.91,376204.30,19.96
+shl,gmpz,1048576,13,55,3,367509.64,367024.53,20.21
+shr,auto,1,13,1814133,9,10.93,10.77,19.80
+shr,inplace,1,13,2930154,9,3.50,3.39,20.47
+shr,kernel,1,13,11752760,9,1.70,1.70,20.04
+shr,gmp,1,13,10588959,9,1.89,1.89,20.01
+shr,gmpz,1,13,5840279,9,3.42,3.41,19.99
+shr,auto,4,13,963006,9,20.69,20.64,19.94
+shr,inplace,4,13,2191285,9,4.52,4.50,19.87
+shr,kernel,4,13,5767765,9,3.47,3.47,20.05
+shr,gmp,4,13,8557117,9,2.33,2.31,19.96
+shr,gmpz,4,13,5048726,9,3.97,3.95,20.05
+shr,auto,16,13,760884,9,26.28,26.16,20.00
+shr,inplace,16,13,1106233,9,9.07,9.03,20.08
+shr,kernel,16,13,1680793,9,11.90,11.90,20.00
+shr,gmp,16,13,4672047,9,4.33,4.30,20.22
+shr,gmpz,16,13,3241265,9,6.21,6.14,20.17
+shr,auto,256,13,144127,9,138.67,138.41,19.99
+shr,inplace,256,13,109697,9,91.06,90.94,19.98
+shr,kernel,256,13,180936,9,110.48,110.40,19.99
+shr,gmp,256,13,414870,9,48.17,47.99,19.98
+shr,gmpz,256,13,394539,9,50.71,50.49,20.03
+shr,auto,2000,13,21528,9,928.88,927.73,20.00
+shr,inplace,2000,13,15441,9,646.16,645.24,19.96
+shr,kernel,2000,13,24623,9,812.36,812.27,20.01
+shr,gmp,2000,13,59521,9,335.78,335.47,19.99
+shr,gmpz,2000,13,59035,9,338.98,338.81,20.01
+shr,auto,2000,77,18469,9,1083.49,1082.88,20.01
+shr,inplace,2000,77,13234,9,754.96,754.74,20.00
+shr,kernel,2000,77,24085,9,827.85,827.76,19.94
+shr,gmp,2000,77,59465,9,336.31,336.15,20.00
+shr,gmpz,2000,77,59002,9,339.12,339.06,20.01
+shr,auto,131072,13,203,3,98533.07,98453.76,20.00
+shr,inplace,131072,13,231,3,43204.90,43101.73,19.96
+shr,kernel,131072,13,262,3,76336.55,76302.85,20.00
+shr,gmp,131072,13,523,3,38298.94,38291.39,20.03
+shr,gmpz,131072,13,534,3,37465.97,37463.04,20.01
+shr,auto,1048576,13,18,3,1132917.94,1131390.67,20.39
+shr,inplace,1048576,13,29,3,349391.43,348333.19,20.25
+shr,kernel,1048576,13,26,3,797959.62,797698.08,20.77
+shr,gmp,1048576,13,52,3,382669.25,382165.62,19.92
+shr,gmpz,1048576,13,52,3,384873.42,383777.77,20.01
+cmp,auto,1,1,13235937,9,1.51,1.51,20.05
+cmp,kernel,1,1,14311276,9,1.40,1.38,20.17
+cmp,gmp,1,1,10588510,9,1.89,1.89,20.02
+cmp,gmpz,1,1,8145109,9,2.46,2.46,20.00
+cmp,auto,4,4,8967734,9,2.23,2.16,20.20
+cmp,kernel,4,4,10340351,9,1.91,1.91,21.26
+cmp,gmp,4,4,5370516,9,3.71,3.59,19.80
+cmp,gmpz,4,4,4603699,9,4.34,4.34,20.02
+cmp,auto,16,16,3904795,9,5.13,5.12,20.13
+cmp,kernel,16,16,2715104,9,7.37,7.37,20.00
+cmp,gmp,16,16,3669192,9,5.45,5.45,20.06
+cmp,gmpz,16,16,2353034,9,8.50,8.50,20.00
+cmp,auto,256,256,306445,9,64.69,64.56,19.84
+cmp,kernel,256,256,329297,9,60.73,60.13,19.98
+cmp,gmp,256,256,335095,9,59.83,59.77,20.06
+cmp,gmpz,256,256,229093,9,87.26,87.15,19.99
+cmp,auto,2000,2000,44230,9,452.39,452.18,20.06
+cmp,kernel,2000,2000,49274,9,405.47,403.44,19.98
+cmp,gmp,2000,2000,48975,9,408.78,408.29,20.02
+cmp,gmpz,2000,2000,31607,9,632.73,632.62,20.00
+cmp,auto,131072,131072,507,3,39439.91,39430.22,19.99
+cmp,kernel,131072,131072,504,3,39695.88,39690.98,20.01
+cmp,gmp,131072,131072,500,3,40011.60,40003.69,20.01
+cmp,gmpz,131072,131072,356,3,56199.62,56195.20,20.01
diff --git a/tests/beman/big_int/perf/gmp_gap_x64_gcc14.csv b/tests/beman/big_int/perf/gmp_gap_x64_gcc14.csv
new file mode 100644
index 00000000..348b614c
--- /dev/null
+++ b/tests/beman/big_int/perf/gmp_gap_x64_gcc14.csv
@@ -0,0 +1,522 @@
+# gap_sweep date 2026-10-06T13:54:26Z host mborland-MS-7C75 Linux x86_64
+# gap_sweep bin builds/gcc14-native/shape_sweep seed=1 ops=add,sub,shl,shr,cmp,mul,sqr,divrem,tochars,fromchars,gcd bands=small,medium,large pin=taskset -c 2
+#const square_long_cutoff=10 karatsuba_cutoff=260 karatsuba_fallback=230 square_karatsuba_cutoff=257 toom_cook_3_cutoff=1600 toom_cook_4_cutoff=4000 toom_cook_6_5_cutoff=4000 toom_cook_8_5_cutoff=6000 square_toom_cook_3_cutoff=4200 square_toom_cook_4_cutoff=6000 square_toom_cook_6_5_cutoff=9000 square_toom_cook_8_5_cutoff=14000 fft_mul_min_limbs=16000 fft_mul_model_num=15 fft_mul_model_den=64 square_fft_min_limbs=8000 square_fft_model_num=75 square_fft_model_den=512 fft_model_log_power=1 fft_model_root_degree=2 fft_cyclic_cutoff=8192 multiply_mod_bnm1_cutoff=16 mul_slice_z0_min=260 mul_slice_z0_num=7 mul_slice_z0_den=4 mul_slice_z1_min=1600 mul_slice_z1_num=3 mul_slice_z1_den=2 mul_slice_z2_min=30000 mul_slice_z2_num=4 mul_slice_z2_den=3 mul_slice_z3_min=0 mul_slice_z3_num=3 mul_slice_z3_den=2 mul_slice_z4_min=0 mul_slice_z4_num=3 mul_slice_z4_den=2 mul_slice_z5_min=0 mul_slice_z5_num=3 mul_slice_z5_den=2 mul_slice_zone_count=3 ifma_multiply_min_limbs=6 ifma_multiply_mid_limbs=8 ifma_multiply_high_limbs=13 ifma_multiply_always_limbs=20 ifma_multiply_min_product_low=900 ifma_multiply_min_product_mid=450 ifma_multiply_min_product_high=330 ifma_square_min_limbs=16 ifma_square_native_max_limbs=256 burnikel_ziegler_cutoff=160 burnikel_ziegler_offset=64 barrett_march_cutoff=512 barrett_march8_cutoff=4096 barrett_quarter_cutoff=49152 barrett_balanced_cutoff=196608 reciprocal_span_cutoff=512 fast_input_basecase_chunks=512 fast_output_basecase_chunks=16 fast_input_charconv_min_chunks=8 fast_output_preinv_min_limbs=98304 ARCH=x86_64 BMI2_ADX=1 AVX512_IFMA=1 SIMD_MUL=1 limb_bits=64 ndebug=1 optimize=1 isa_bmi2=1 isa_adx=1 isa_avx2=1 isa_avx512f=1 isa_avx512ifma=1 compiler=gcc-14.3.0 via=cmake build_type=RelWithDebInfo cxx_flags=-march=native_-O3__-O2_-g_-DNDEBUG_defs:
+op,path,la,lb,reps,rounds,median_ns,min_ns,round_ms_actual
+add,auto,1,1,2450177,9,8.18,8.14,20.03
+add,inplace,1,1,2463984,9,3.87,3.83,19.07
+add,kernel,1,1,9644328,9,2.07,2.03,19.88
+add,gmp,1,1,8823972,9,2.27,2.27,20.00
+add,gmpz,1,1,4813002,9,4.16,4.16,20.00
+add,auto,2,2,1180018,9,16.93,16.82,20.00
+add,inplace,2,2,1884590,9,5.32,5.30,20.06
+add,kernel,2,2,8245467,9,2.43,2.38,20.11
+add,gmp,2,2,8824112,9,2.27,2.27,20.00
+add,gmpz,2,2,4813089,9,4.16,4.16,20.00
+add,auto,4,4,1155526,9,17.27,17.24,19.97
+add,inplace,4,4,1885208,9,5.33,5.30,20.13
+add,kernel,4,4,6305206,9,3.15,3.06,19.98
+add,gmp,4,4,8009204,9,2.50,2.50,20.01
+add,gmpz,4,4,4603785,9,4.34,4.34,20.02
+add,auto,8,8,1067825,9,18.69,18.52,19.93
+add,inplace,8,8,1409927,9,7.07,7.05,19.96
+add,kernel,8,8,4355415,9,4.57,4.53,19.92
+add,gmp,8,8,5425132,9,3.69,3.69,20.01
+add,gmpz,8,8,4300948,9,4.66,4.64,20.10
+add,auto,16,16,775188,9,25.64,25.28,19.84
+add,inplace,16,16,890946,9,11.24,11.17,20.03
+add,kernel,16,16,2584030,9,7.74,7.69,20.03
+add,gmp,16,16,3552672,9,5.63,5.63,20.02
+add,gmpz,16,16,3072066,9,6.53,6.51,20.06
+add,auto,64,64,322170,9,62.25,62.06,20.06
+add,inplace,64,64,254817,9,39.32,39.16,20.02
+add,kernel,64,64,594354,9,33.65,33.63,20.02
+add,gmp,64,64,1294833,9,15.45,15.45,20.02
+add,gmpz,64,64,1212104,9,16.50,16.47,20.00
+add,auto,256,256,89554,9,223.21,223.16,19.99
+add,inplace,256,256,68319,9,146.10,146.02,19.96
+add,kernel,256,256,140245,9,142.60,142.55,20.02
+add,gmp,256,256,255610,9,78.24,78.24,20.09
+add,gmpz,256,256,244850,9,79.92,79.39,19.79
+add,auto,1024,1024,23879,9,838.05,837.60,20.03
+add,inplace,1024,1024,17162,9,582.70,582.66,20.00
+add,kernel,1024,1024,34606,9,577.94,577.93,20.00
+add,gmp,1024,1024,51272,9,390.08,390.04,20.01
+add,gmpz,1024,1024,51164,9,390.92,390.88,20.02
+add,auto,2000,2000,11481,9,1738.52,1737.90,19.96
+add,inplace,2000,2000,8814,9,1134.52,1134.28,20.00
+add,kernel,2000,2000,17674,9,1131.67,1131.64,20.01
+add,gmp,2000,2000,43698,9,457.66,457.57,20.04
+add,gmpz,2000,2000,43482,9,459.81,457.88,20.02
+add,auto,1024,1,71391,9,280.75,280.26,20.04
+add,inplace,1024,1,17265,9,579.20,579.18,20.00
+add,kernel,1024,1,34474,9,580.16,580.14,20.01
+add,gmp,1024,1,85967,9,232.61,231.81,19.98
+add,gmpz,1024,1,85471,9,233.98,233.53,19.99
+add,auto,16384,16384,1349,3,14826.74,14826.66,20.00
+add,inplace,16384,16384,1074,3,9316.61,9315.91,20.01
+add,kernel,16384,16384,2150,3,9307.58,9306.14,20.01
+add,gmp,16384,16384,2881,3,6943.54,6942.80,20.01
+add,gmpz,16384,16384,2673,3,7483.38,7482.10,20.00
+add,auto,131072,131072,157,3,127993.49,127877.06,20.10
+add,inplace,131072,131072,135,3,74401.93,74390.15,20.10
+add,kernel,131072,131072,269,3,74355.78,74355.16,20.00
+add,gmp,131072,131072,342,3,58523.98,58510.80,20.01
+add,gmpz,131072,131072,341,3,58828.05,58813.53,20.06
+add,auto,1048576,1048576,17,3,1162356.76,1161150.47,19.80
+add,inplace,1048576,1048576,16,3,640912.19,640507.78,20.51
+add,kernel,1048576,1048576,27,3,752674.41,750660.37,20.32
+add,gmp,1048576,1048576,30,3,687817.93,687692.80,20.65
+add,gmpz,1048576,1048576,29,3,697281.76,695932.14,20.22
+sub,auto,1,1,2155566,9,9.28,9.27,20.02
+sub,inplace,1,1,2696390,9,3.72,3.69,20.01
+sub,kernel,1,1,13161727,9,1.51,1.51,19.91
+sub,gmp,1,1,8824227,9,2.27,2.27,20.01
+sub,gmpz,1,1,4594385,9,4.35,4.34,20.01
+sub,auto,2,2,1116318,9,18.03,17.98,20.14
+sub,inplace,2,2,2298545,9,4.39,4.35,20.21
+sub,kernel,2,2,9626567,9,2.08,2.08,20.00
+sub,gmp,2,2,8824015,9,2.27,2.27,20.00
+sub,gmpz,2,2,4603923,9,4.34,4.34,20.01
+sub,auto,4,4,1081012,9,18.51,18.47,20.02
+sub,inplace,4,4,1918292,9,5.21,5.20,20.04
+sub,kernel,4,4,7058825,9,2.83,2.83,20.00
+sub,gmp,4,4,8043039,9,2.49,2.49,20.03
+sub,gmpz,4,4,4371277,9,4.59,4.55,20.04
+sub,auto,8,8,1000135,9,19.95,19.86,19.95
+sub,inplace,8,8,1446815,9,6.89,6.87,19.94
+sub,kernel,8,8,4594585,9,4.35,4.35,20.02
+sub,gmp,8,8,5437984,9,3.68,3.68,20.03
+sub,gmpz,8,8,4036387,9,4.96,4.94,20.09
+sub,auto,16,16,808186,9,24.77,24.28,20.02
+sub,inplace,16,16,976593,9,10.23,10.21,19.98
+sub,kernel,16,16,2678030,9,7.47,7.47,20.01
+sub,gmp,16,16,3630469,9,5.50,5.50,19.97
+sub,gmpz,16,16,2935657,9,6.81,6.79,20.00
+sub,auto,64,64,310901,9,64.11,63.93,19.94
+sub,inplace,64,64,264488,9,37.78,37.74,20.01
+sub,kernel,64,64,591302,9,33.73,33.46,19.92
+sub,gmp,64,64,1284455,9,15.57,15.57,20.00
+sub,gmpz,64,64,1197285,9,16.70,16.67,19.99
+sub,auto,256,256,87120,9,229.48,229.41,19.99
+sub,inplace,256,256,68967,9,145.02,144.98,20.00
+sub,kernel,256,256,140109,9,142.71,142.66,20.00
+sub,gmp,256,256,255688,9,78.22,78.22,20.00
+sub,gmpz,256,256,250501,9,79.84,79.73,20.00
+sub,auto,1024,1024,23501,9,848.21,848.05,19.96
+sub,inplace,1024,1024,17211,9,581.05,580.99,20.01
+sub,kernel,1024,1024,34592,9,578.46,578.15,20.01
+sub,gmp,1024,1024,51321,9,389.73,389.70,20.00
+sub,gmpz,1024,1024,51149,9,391.03,391.00,20.01
+sub,auto,2000,2000,11465,9,1741.91,1741.55,19.98
+sub,inplace,2000,2000,8822,9,1133.58,1133.47,20.01
+sub,kernel,2000,2000,17662,9,1132.54,1132.51,20.00
+sub,gmp,2000,2000,42167,9,474.31,474.29,20.02
+sub,gmpz,2000,2000,41219,9,485.18,483.87,20.01
+sub,auto,1024,1,70698,9,283.21,283.09,20.03
+sub,inplace,1024,1,17266,9,579.21,579.18,20.00
+sub,kernel,1024,1,34676,9,576.78,576.76,20.00
+sub,gmp,1024,1,85714,9,233.40,233.35,20.01
+sub,gmpz,1024,1,85339,9,234.33,233.91,19.99
+sub,auto,16384,16384,1336,3,14967.33,14963.99,20.00
+sub,inplace,16384,16384,1075,3,9307.89,9307.88,20.01
+sub,kernel,16384,16384,2152,3,9292.47,9292.26,20.00
+sub,gmp,16384,16384,2930,3,6826.63,6826.34,20.01
+sub,gmpz,16384,16384,2893,3,6913.05,6912.31,20.00
+sub,auto,131072,131072,157,3,127993.15,127986.77,20.10
+sub,inplace,131072,131072,134,3,74379.15,74379.11,19.93
+sub,kernel,131072,131072,269,3,74363.94,74363.60,20.00
+sub,gmp,131072,131072,335,3,59713.80,59712.77,20.01
+sub,gmpz,131072,131072,342,3,58632.49,58615.51,20.05
+sub,auto,1048576,1048576,17,3,1178351.18,1174255.29,20.04
+sub,inplace,1048576,1048576,16,3,640266.34,640162.31,20.49
+sub,kernel,1048576,1048576,27,3,753340.44,752429.07,20.35
+sub,gmp,1048576,1048576,30,3,685451.27,684540.30,20.59
+sub,gmpz,1048576,1048576,29,3,699342.34,696701.21,20.26
+shl,auto,1,13,1200358,9,16.63,16.49,20.00
+shl,inplace,1,13,2477081,9,4.08,4.05,20.33
+shl,kernel,1,13,9626758,9,2.08,2.07,20.00
+shl,kernelip,1,13,5824436,9,1.73,1.72,20.29
+shl,gmp,1,13,9626422,9,2.08,2.08,20.00
+shl,gmpz,1,13,5881529,9,3.40,3.40,20.00
+shl,auto,4,13,1092154,9,18.36,18.20,20.03
+shl,inplace,4,13,1906151,9,5.17,5.15,19.73
+shl,kernel,4,13,5989568,9,3.35,3.33,20.08
+shl,kernelip,4,13,3426810,9,2.94,2.91,20.24
+shl,gmp,4,13,8145052,9,2.46,2.28,19.89
+shl,gmpz,4,13,5038647,9,3.97,3.97,20.01
+shl,auto,16,13,818044,9,24.33,24.23,19.90
+shl,inplace,16,13,1087656,9,9.20,9.16,20.01
+shl,kernel,16,13,2740699,9,7.32,7.30,20.10
+shl,kernelip,16,13,1376775,9,7.19,7.10,19.77
+shl,gmp,16,13,4848874,9,4.07,4.07,19.78
+shl,gmpz,16,13,3458135,9,5.79,5.78,20.02
+shl,auto,256,13,138291,9,144.68,143.24,20.01
+shl,inplace,256,13,107445,9,93.00,92.81,20.00
+shl,kernel,256,13,213963,9,93.42,93.32,19.99
+shl,kernelip,256,13,102313,9,97.73,97.72,20.02
+shl,gmp,256,13,423532,9,47.22,47.14,20.00
+shl,gmpz,256,13,397070,9,50.28,49.99,19.96
+shl,auto,2000,13,21764,9,918.94,918.19,20.00
+shl,inplace,2000,13,15220,9,657.03,656.06,20.05
+shl,kernel,2000,13,27721,9,718.06,717.92,19.93
+shl,kernelip,2000,13,13979,9,715.33,715.32,20.02
+shl,gmp,2000,13,59557,9,335.47,335.43,19.98
+shl,gmpz,2000,13,59028,9,338.79,338.68,20.00
+shl,auto,2000,77,18795,9,1066.24,1062.47,20.04
+shl,inplace,2000,77,13040,9,766.90,766.06,20.00
+shl,kernel,2000,77,23454,9,851.08,850.36,19.97
+shl,kernelip,2000,77,12165,9,822.05,822.03,20.00
+shl,gmp,2000,77,59286,9,337.35,337.34,20.00
+shl,gmpz,2000,77,58730,9,339.38,339.31,19.95
+shl,auto,131072,13,200,3,99966.49,99934.70,19.99
+shl,inplace,131072,13,231,3,43898.96,43558.73,20.24
+shl,kernel,131072,13,284,3,70565.07,70545.41,20.04
+shl,kernelip,131072,13,215,3,46555.48,46555.03,20.02
+shl,gmp,131072,13,589,3,34009.04,33992.41,20.04
+shl,gmpz,131072,13,590,3,33925.27,33923.97,20.02
+shl,auto,1048576,13,18,3,1142281.39,1141451.17,20.56
+shl,inplace,1048576,13,29,3,351798.60,351457.84,20.53
+shl,kernel,1048576,13,28,3,725814.71,723464.71,20.34
+shl,kernelip,1048576,13,27,3,373539.94,373537.44,20.17
+shl,gmp,1048576,13,54,3,375011.30,374564.87,20.24
+shl,gmpz,1048576,13,54,3,373228.00,371794.87,20.15
+shr,auto,1,13,1935951,9,10.33,10.21,19.97
+shr,inplace,1,13,2718469,9,3.72,3.63,20.16
+shr,kernel,1,13,11754427,9,1.70,1.70,20.01
+shr,gmp,1,13,10588926,9,1.89,1.89,20.01
+shr,gmpz,1,13,6228558,9,3.21,3.21,20.01
+shr,auto,4,13,1005957,9,19.92,19.82,20.04
+shr,inplace,4,13,2103403,9,4.71,4.70,19.92
+shr,kernel,4,13,5785585,9,3.41,3.41,19.77
+shr,gmp,4,13,8527843,9,2.31,2.30,19.68
+shr,gmpz,4,13,5064223,9,3.93,3.91,19.91
+shr,auto,16,13,795938,9,25.16,24.83,20.05
+shr,inplace,16,13,1143178,9,8.73,8.70,19.96
+shr,kernel,16,13,1680738,9,11.90,11.90,20.00
+shr,gmp,16,13,2654207,9,7.53,7.53,20.01
+shr,gmpz,16,13,2472362,9,8.09,8.08,20.01
+shr,auto,256,13,145903,9,136.73,136.31,19.94
+shr,inplace,256,13,106514,9,93.89,93.67,20.07
+shr,kernel,256,13,181260,9,110.51,110.38,20.04
+shr,gmp,256,13,405575,9,48.69,48.60,19.75
+shr,gmpz,256,13,389973,9,51.51,51.15,20.07
+shr,auto,2000,13,21088,9,949.62,948.68,20.02
+shr,inplace,2000,13,15220,9,659.30,657.01,20.06
+shr,kernel,2000,13,24598,9,813.22,813.05,20.01
+shr,gmp,2000,13,59565,9,335.99,335.63,20.02
+shr,gmpz,2000,13,59010,9,338.92,338.82,20.00
+shr,auto,2000,77,18461,9,1083.72,1082.30,20.02
+shr,inplace,2000,77,13055,9,766.74,766.05,20.02
+shr,kernel,2000,77,24154,9,828.13,827.94,20.00
+shr,gmp,2000,77,59447,9,336.35,336.28,20.00
+shr,gmpz,2000,77,58951,9,339.25,339.14,20.00
+shr,auto,131072,13,203,3,98968.31,98962.89,20.09
+shr,inplace,131072,13,230,3,43619.95,43596.10,20.07
+shr,kernel,131072,13,263,3,76120.12,76107.47,20.02
+shr,gmp,131072,13,508,3,39394.32,39376.61,20.01
+shr,gmpz,131072,13,522,3,38385.72,38374.51,20.04
+shr,auto,1048576,13,18,3,1125734.39,1123746.50,20.27
+shr,inplace,1048576,13,29,3,351717.81,351615.21,20.40
+shr,kernel,1048576,13,26,3,797793.31,797787.12,20.75
+shr,gmp,1048576,13,54,3,373804.31,373123.04,20.35
+shr,gmpz,1048576,13,53,3,377401.36,376751.51,20.00
+cmp,auto,1,1,13236118,9,1.51,1.51,20.07
+cmp,kernel,1,1,14849654,9,1.36,1.35,20.36
+cmp,gmp,1,1,10589173,9,1.89,1.89,20.01
+cmp,gmpz,1,1,8145481,9,2.46,2.46,20.00
+cmp,auto,4,4,9384579,9,2.18,2.12,20.40
+cmp,kernel,4,4,9570817,9,2.06,2.06,19.81
+cmp,gmp,4,4,5572928,9,3.59,3.59,20.00
+cmp,gmpz,4,4,4603932,9,4.34,4.34,20.02
+cmp,auto,16,16,3789614,9,5.28,5.27,20.02
+cmp,kernel,16,16,3479439,9,5.75,5.75,20.04
+cmp,gmp,16,16,3671422,9,5.45,5.44,20.01
+cmp,gmpz,16,16,2353131,9,8.50,8.50,20.00
+cmp,auto,256,256,306095,9,65.27,65.19,19.98
+cmp,kernel,256,256,328840,9,60.88,60.80,20.01
+cmp,gmp,256,256,334363,9,59.76,59.69,19.98
+cmp,gmpz,256,256,225046,9,88.86,88.74,20.03
+cmp,auto,2000,2000,42770,9,467.69,466.45,20.01
+cmp,kernel,2000,2000,49287,9,405.59,403.90,19.98
+cmp,gmp,2000,2000,48858,9,408.38,408.00,19.96
+cmp,gmpz,2000,2000,31614,9,632.53,632.39,20.01
+cmp,auto,131072,131072,507,3,39472.83,39469.91,20.02
+cmp,kernel,131072,131072,440,3,45508.61,45505.34,20.02
+cmp,gmp,131072,131072,500,3,40047.79,40043.64,20.02
+cmp,gmpz,131072,131072,357,3,56139.80,56138.39,20.04
+mul,auto,1,1,1008159,9,19.86,19.78,20.02
+mul,gmp,1,1,5884618,9,3.39,3.37,19.94
+mul,gmpz,1,1,5039680,9,3.97,3.97,19.99
+mul,auto,2,2,716107,9,28.29,27.94,20.28
+mul,kernel,2,2,2872625,9,7.42,7.39,21.31
+mul,gmp,2,2,5271831,9,3.79,3.78,20.00
+mul,gmpz,2,2,3332522,9,6.04,6.00,20.14
+mul,auto,3,3,668288,9,29.73,29.63,19.92
+mul,kernel,3,3,2427367,9,8.78,8.77,21.34
+mul,gmp,3,3,2547778,9,7.77,7.69,19.86
+mul,gmpz,3,3,1990720,9,9.82,9.78,19.56
+mul,auto,4,4,618993,9,31.57,31.38,19.59
+mul,kernel,4,4,1961241,9,10.78,10.75,21.14
+mul,gmp,4,4,2100710,9,9.51,9.49,20.00
+mul,gmpz,4,4,1676566,9,11.95,11.91,20.05
+mul,auto,6,6,524822,9,37.99,37.96,20.01
+mul,kernel,6,6,1228471,9,17.15,17.14,21.07
+mul,gmp,6,6,1181690,9,16.97,16.92,20.07
+mul,gmpz,6,6,1120346,9,17.88,17.84,20.03
+mul,auto,8,8,434412,9,46.06,45.95,20.03
+mul,kernel,8,8,827802,9,24.99,24.85,20.67
+mul,gmp,8,8,737742,9,26.97,26.86,19.94
+mul,gmpz,8,8,697976,9,28.97,28.72,20.22
+mul,auto,12,12,284641,9,70.24,70.20,20.01
+mul,kernel,12,12,430091,9,47.02,46.96,20.23
+mul,gmp,12,12,411366,9,48.55,48.54,19.97
+mul,gmpz,12,12,394211,9,50.75,50.74,20.05
+mul,auto,16,16,197443,9,101.06,100.94,19.95
+mul,kernel,16,16,255821,9,78.77,78.70,20.16
+mul,gmp,16,16,244394,9,81.90,81.85,20.01
+mul,gmpz,16,16,237880,9,84.07,84.05,20.00
+mul,auto,32,32,91874,9,217.24,215.48,19.99
+mul,kernel,32,32,105814,9,189.15,187.65,20.01
+mul,gmp,32,32,74499,9,268.38,268.34,19.99
+mul,gmpz,32,32,73795,9,271.10,270.97,20.03
+mul,auto,64,64,38453,9,521.77,521.02,20.09
+mul,kernel,64,64,40884,9,487.91,487.33,19.96
+mul,gmp,64,64,23254,9,860.23,859.91,20.00
+mul,gmpz,64,64,23233,9,860.97,860.50,20.01
+mul,auto,128,128,11473,9,1753.62,1742.62,20.11
+mul,kernel,128,128,11787,9,1709.36,1699.24,20.14
+mul,gmp,128,128,7328,9,2729.30,2729.05,20.00
+mul,gmpz,128,128,7375,9,2712.14,2710.98,20.00
+mul,auto,256,256,3186,9,6286.79,6268.81,20.09
+mul,kernel,256,256,3232,9,6211.21,6201.29,20.08
+mul,gmp,256,256,2576,9,7764.96,7763.53,20.00
+mul,gmpz,256,256,2575,9,7769.63,7766.05,20.01
+mul,auto,512,512,1019,9,19639.38,19534.01,20.02
+mul,kernel,512,512,1020,9,19366.78,19280.49,19.75
+mul,gmp,512,512,960,9,20833.27,20821.34,20.00
+mul,gmpz,512,512,961,9,20810.12,20798.32,20.00
+mul,auto,1000,1000,328,9,60344.96,60078.84,19.79
+mul,kernel,1000,1000,330,9,60562.87,60145.72,19.98
+mul,gmp,1000,1000,375,9,53377.56,53369.91,20.02
+mul,gmpz,1000,1000,376,9,53250.16,53224.18,20.03
+mul,auto,2000,2000,113,9,178209.68,177634.02,20.13
+mul,kernel,2000,2000,113,9,177651.96,177113.30,20.08
+mul,gmp,2000,2000,139,9,143989.49,143975.96,20.01
+mul,gmpz,2000,2000,139,9,144036.96,143962.03,20.02
+mul,auto,2000,100,1121,9,17841.88,17833.85,20.06
+mul,kernel,2000,100,1133,9,17670.20,17665.49,20.03
+mul,gmp,2000,100,559,9,35798.69,35736.46,20.00
+mul,gmpz,2000,100,559,9,35807.68,35794.46,20.02
+mul,auto,4096,4096,39,3,520724.82,511577.49,20.20
+mul,kernel,4096,4096,40,3,509292.53,508637.95,20.36
+mul,gmp,4096,4096,52,3,388168.10,388040.52,20.19
+mul,gmpz,4096,4096,52,3,388259.17,388110.90,20.19
+mul,auto,16384,16384,6,3,3773355.33,3771457.83,22.64
+mul,kernel,16384,16384,6,3,3766070.00,3765167.83,22.60
+mul,gmp,16384,16384,12,3,1805965.92,1800956.58,21.66
+mul,gmpz,16384,16384,12,3,1766784.42,1764642.58,21.20
+mul,auto,65536,65536,1,3,24498582.00,24498334.00,24.52
+mul,kernel,65536,65536,1,3,24509522.00,24430802.00,24.49
+mul,gmp,65536,65536,3,3,9051790.67,8949936.67,27.07
+mul,gmpz,65536,65536,3,3,8605965.00,8602567.00,25.82
+mul,auto,262144,262144,1,3,138894692.00,138219158.00,138.81
+mul,kernel,262144,262144,1,3,135766931.00,135316401.00,135.79
+mul,gmp,262144,262144,1,3,50089269.00,49836700.00,50.04
+mul,gmpz,262144,262144,1,3,49717967.00,49676233.00,49.72
+mul,auto,1048576,1048576,1,3,750359828.00,748603708.00,749.82
+mul,kernel,1048576,1048576,1,3,749878642.00,747932601.00,750.08
+mul,gmp,1048576,1048576,1,3,246373599.00,245895200.00,246.25
+mul,gmpz,1048576,1048576,1,3,246594020.00,246520690.00,246.58
+mul,auto,262144,8192,1,3,44414876.00,44294575.00,44.38
+mul,kernel,262144,8192,1,3,59519885.00,59410653.00,59.48
+mul,gmp,262144,8192,2,3,19838756.00,19837472.00,39.68
+mul,gmpz,262144,8192,2,3,19126247.00,19106720.00,38.44
+sqr,auto,4,4,606683,9,33.09,32.88,20.11
+sqr,kernel,4,4,2211782,9,8.94,8.91,19.82
+sqr,gmp,4,4,3496886,9,5.70,5.67,19.93
+sqr,gmpz,4,4,2311212,9,8.66,8.64,20.03
+sqr,auto,16,16,248654,9,81.23,80.84,20.19
+sqr,kernel,16,16,345990,9,58.04,57.75,20.08
+sqr,gmp,16,16,383561,9,51.86,51.84,19.89
+sqr,gmpz,16,16,384004,9,52.09,52.05,20.00
+sqr,auto,64,64,66777,9,298.64,298.17,19.96
+sqr,kernel,64,64,70183,9,284.55,283.97,19.97
+sqr,gmp,64,64,36286,9,551.34,551.02,20.02
+sqr,gmpz,64,64,36173,9,552.97,552.73,20.01
+sqr,auto,512,512,1878,9,10646.78,10643.63,20.00
+sqr,kernel,512,512,1899,9,10538.36,10526.12,20.01
+sqr,gmp,512,512,1320,9,15151.00,15149.99,20.01
+sqr,gmpz,512,512,1319,9,15157.86,15150.17,20.00
+sqr,auto,2000,2000,201,9,99500.50,99089.05,20.00
+sqr,kernel,2000,2000,205,9,97666.30,97635.39,20.02
+sqr,gmp,2000,2000,199,9,100605.63,100539.16,20.03
+sqr,gmpz,2000,2000,199,9,100604.13,100543.74,20.03
+sqr,auto,16384,16384,9,3,2413895.78,2409981.33,21.74
+sqr,kernel,16384,16384,9,3,2402054.22,2397284.78,21.61
+sqr,gmp,16384,16384,18,3,1147473.61,1147198.94,20.66
+sqr,gmpz,16384,16384,18,3,1148184.94,1148032.83,20.67
+sqr,auto,262144,262144,1,3,111749213.00,111587833.00,111.72
+sqr,kernel,262144,262144,1,3,111563510.00,111551544.00,111.57
+sqr,gmp,262144,262144,1,3,32798749.00,32791788.00,32.81
+sqr,gmpz,262144,262144,1,3,32851135.00,32778398.00,32.83
+sqr,auto,1048576,1048576,1,3,599436373.00,598664677.00,603.10
+sqr,kernel,1048576,1048576,1,3,597065259.00,596519673.00,597.61
+sqr,gmp,1048576,1048576,1,3,165249772.00,165189333.00,165.25
+sqr,gmpz,1048576,1048576,1,3,165339227.00,165165516.00,165.43
+divrem,auto,2,1,471919,9,42.53,42.38,20.09
+divrem,kernel,2,1,3123240,9,6.37,6.29,19.91
+divrem,gmp,2,1,2202801,9,9.10,9.07,20.05
+divrem,gmpz,2,1,1515623,9,13.18,13.14,19.97
+divrem,auto,8,4,191105,9,105.86,102.35,20.10
+divrem,kernel,8,4,522295,9,38.44,38.25,20.10
+divrem,gmp,8,4,503727,9,40.22,40.06,20.25
+divrem,gmpz,8,4,480416,9,41.62,41.33,20.00
+divrem,auto,32,16,54433,9,368.30,367.03,20.05
+divrem,kernel,32,16,69636,9,287.59,286.85,20.03
+divrem,gmp,32,16,104556,9,191.67,191.11,20.04
+divrem,gmpz,32,16,103106,9,194.45,193.64,20.04
+divrem,auto,128,64,5901,9,3335.54,3334.46,19.68
+divrem,kernel,128,64,6268,9,3191.09,3190.05,20.00
+divrem,gmp,128,64,11620,9,1720.79,1720.43,20.00
+divrem,gmpz,128,64,11611,9,1722.84,1721.86,20.05
+divrem,auto,512,256,680,9,29413.93,29403.08,20.01
+divrem,kernel,512,256,691,9,28968.59,28958.45,20.02
+divrem,gmp,512,256,1233,9,16227.48,16220.49,20.01
+divrem,gmpz,512,256,1232,9,16246.50,16245.13,20.03
+divrem,auto,2000,1000,110,9,182665.98,182125.39,20.09
+divrem,kernel,2000,1000,110,9,181815.25,181766.49,20.00
+divrem,gmp,2000,1000,146,9,137762.13,137737.51,20.11
+divrem,gmpz,2000,1000,146,9,137762.43,137748.64,20.14
+divrem,auto,4000,2000,43,9,470159.33,469111.09,20.22
+divrem,kernel,4000,2000,43,9,467704.81,466898.49,20.14
+divrem,gmp,4000,2000,60,9,334269.62,333997.23,20.05
+divrem,gmpz,4000,2000,60,9,333729.02,333669.12,20.04
+divrem,auto,2000,100,140,9,143657.40,143595.36,20.11
+divrem,kernel,2000,100,141,9,142627.72,142601.67,20.12
+divrem,gmp,2000,100,276,9,72649.04,72620.36,20.05
+divrem,gmpz,2000,100,276,9,72662.14,72631.27,20.06
+divrem,auto,65536,1,103,9,194421.67,193963.91,20.07
+divrem,kernel,65536,1,120,9,166849.97,166760.05,20.04
+divrem,gmp,65536,1,136,9,148101.82,148095.99,20.14
+divrem,gmpz,65536,1,136,9,148114.14,148103.54,20.15
+divrem,auto,16384,8192,6,3,3896281.83,3892782.00,23.39
+divrem,kernel,16384,8192,6,3,3881379.17,3877014.67,23.29
+divrem,gmp,16384,8192,10,3,2117426.00,2115008.20,21.17
+divrem,gmpz,16384,8192,10,3,2115871.00,2113900.20,21.15
+divrem,auto,131072,65536,1,3,77397140.00,77337029.00,77.49
+divrem,kernel,131072,65536,1,3,77450684.00,77378955.00,77.44
+divrem,gmp,131072,65536,1,3,24281306.00,24279407.00,24.29
+divrem,gmpz,131072,65536,1,3,24263362.00,24251476.00,24.26
+divrem,auto,1048576,524288,1,3,1370899694.00,1369372240.00,1370.47
+divrem,kernel,1048576,524288,1,3,1371236152.00,1370997918.00,1371.71
+divrem,gmp,1048576,524288,1,3,292398291.00,292383567.00,292.41
+divrem,gmpz,1048576,524288,1,3,292416913.00,292304557.00,292.39
+tochars,auto,1,10,1354953,9,14.75,14.73,20.01
+tochars,kernel,1,10,321459,9,62.09,61.93,19.98
+tochars,gmp,1,10,956748,9,20.75,20.50,19.83
+tochars,gmpz,1,10,589209,9,33.90,33.82,19.97
+tochars,auto,4,10,157961,9,126.76,125.91,20.02
+tochars,kernel,4,10,111323,9,180.97,178.92,20.10
+tochars,gmp,4,10,289397,9,69.08,69.06,19.99
+tochars,gmpz,4,10,209237,9,95.53,95.37,19.99
+tochars,auto,16,10,17184,9,1161.69,1156.86,19.97
+tochars,kernel,16,10,19275,9,1039.87,1035.11,20.03
+tochars,gmp,16,10,39690,9,503.77,503.64,20.00
+tochars,gmpz,16,10,33899,9,590.76,588.19,20.02
+tochars,auto,64,10,3532,9,5628.14,5618.70,19.88
+tochars,kernel,64,10,3792,9,5275.29,5262.96,20.00
+tochars,gmp,64,10,7499,9,2668.60,2665.21,20.02
+tochars,gmpz,64,10,6707,9,2986.23,2984.20,20.03
+tochars,auto,256,10,557,9,35949.02,35923.07,20.08
+tochars,kernel,256,10,579,9,34599.30,34570.40,20.05
+tochars,gmp,256,10,1077,9,18557.92,18537.48,19.99
+tochars,gmpz,256,10,1012,9,19679.02,19668.96,19.92
+tochars,auto,2000,10,33,9,607454.24,607265.58,20.05
+tochars,kernel,2000,10,35,9,584446.06,583935.71,20.45
+tochars,gmp,2000,10,50,9,401063.14,400711.52,20.05
+tochars,gmpz,2000,10,49,9,409562.22,409347.98,20.07
+tochars,auto,16384,10,2,3,12791417.00,12784154.50,25.59
+tochars,kernel,16384,10,2,3,12688172.00,12676654.00,25.38
+tochars,gmp,16384,10,3,3,8363521.33,8356215.33,25.09
+tochars,gmpz,16384,10,3,3,8428589.00,8427765.67,25.29
+tochars,auto,131072,10,1,3,271231476.00,270943300.00,271.22
+tochars,kernel,131072,10,1,3,270134620.00,270036320.00,270.15
+tochars,gmp,131072,10,1,3,122233860.00,122080884.00,122.19
+tochars,gmpz,131072,10,1,3,122709952.00,122683194.00,122.84
+tochars,auto,262144,10,1,3,803962040.00,803657474.00,805.84
+tochars,kernel,262144,10,1,3,798225970.00,798085961.00,798.58
+tochars,gmp,262144,10,1,3,288907302.00,288799808.00,288.89
+tochars,gmpz,262144,10,1,3,290349295.00,289953421.00,290.29
+tochars,auto,16,16,188828,9,105.57,105.48,19.93
+tochars,gmp,16,16,201151,9,99.41,99.22,19.99
+tochars,gmpz,16,16,126157,9,158.52,158.49,20.00
+tochars,auto,2000,16,1677,9,11929.70,11929.09,20.01
+tochars,gmp,2000,16,1801,9,11111.20,11110.57,20.02
+tochars,gmpz,2000,16,1116,9,17920.70,17919.64,20.00
+tochars,auto,131072,16,26,3,779747.42,779731.65,20.27
+tochars,gmp,131072,16,27,3,744530.11,744389.15,20.10
+tochars,gmpz,131072,16,18,3,1174925.56,1173333.94,21.21
+fromchars,auto,4,10,105727,9,186.43,185.63,19.75
+fromchars,kernel,4,10,533902,9,37.52,37.44,20.03
+fromchars,gmp,4,10,637783,9,31.44,31.25,20.05
+fromchars,gmpz,4,10,299448,9,66.69,66.37,19.95
+fromchars,auto,16,10,47638,9,417.65,413.05,19.96
+fromchars,kernel,16,10,95713,9,215.25,208.51,20.61
+fromchars,gmp,16,10,157759,9,124.51,124.26,19.71
+fromchars,gmpz,16,10,78243,9,256.96,255.05,20.10
+fromchars,auto,64,10,6834,9,2847.91,2740.85,19.55
+fromchars,kernel,64,10,8432,9,2483.10,2235.39,20.75
+fromchars,gmp,64,10,23048,9,867.84,867.32,20.00
+fromchars,gmpz,64,10,14720,9,1359.37,1358.35,20.03
+fromchars,auto,256,10,711,9,29118.60,28124.29,20.78
+fromchars,kernel,256,10,782,9,26569.82,25490.48,20.95
+fromchars,gmp,256,10,2514,9,7953.24,7950.14,19.99
+fromchars,gmpz,256,10,2013,9,9944.03,9935.33,20.03
+fromchars,auto,2000,10,48,9,422852.83,422792.12,20.30
+fromchars,kernel,2000,10,50,9,402177.76,401922.86,20.11
+fromchars,gmp,2000,10,110,9,182581.15,182495.11,20.08
+fromchars,gmpz,2000,10,102,9,197282.45,197198.86,20.12
+fromchars,auto,16384,10,3,3,7449791.00,7441585.67,22.34
+fromchars,kernel,16384,10,3,3,7309958.67,7297628.00,21.93
+fromchars,gmp,16384,10,6,3,3719969.00,3719788.17,22.32
+fromchars,gmpz,16384,10,6,3,3842849.50,3841194.83,23.05
+fromchars,auto,131072,10,1,3,143020658.00,142909988.00,143.01
+fromchars,kernel,131072,10,1,3,141226559.00,140977551.00,141.23
+fromchars,gmp,131072,10,1,3,50147438.00,50109928.00,50.14
+fromchars,gmpz,131072,10,1,3,51160266.00,51155827.00,51.16
+gcd,auto,2,2,38948,9,512.37,509.32,19.95
+gcd,kernel,2,2,41313,9,449.23,444.69,18.91
+gcd,gmp,2,2,157225,9,125.71,124.23,19.79
+gcd,gmpz,2,2,148000,9,135.29,134.62,20.04
+gcd,auto,4,4,24715,9,810.75,808.76,20.05
+gcd,kernel,4,4,27413,9,727.29,725.54,19.93
+gcd,gmp,4,4,37646,9,529.82,527.98,19.94
+gcd,gmpz,4,4,37168,9,538.11,537.55,20.00
+gcd,auto,16,16,5580,9,3586.77,3575.11,20.02
+gcd,kernel,16,16,5760,9,3470.12,3461.43,19.98
+gcd,gmp,16,16,7757,9,2576.37,2573.29,19.99
+gcd,gmpz,16,16,7686,9,2600.62,2599.05,19.99
+gcd,auto,64,64,1047,9,18365.69,18220.97,19.26
+gcd,kernel,64,64,1079,9,18148.98,17901.49,19.68
+gcd,gmp,64,64,1578,9,12682.84,12676.39,20.01
+gcd,gmpz,64,64,1567,9,12763.91,12754.29,20.00
+gcd,auto,256,256,104,9,193328.45,192451.37,20.11
+gcd,kernel,256,256,107,9,187084.53,186904.04,20.03
+gcd,gmp,256,256,215,9,92847.63,92550.24,19.98
+gcd,gmpz,256,256,217,9,92380.27,92094.04,20.05
+gcd,auto,1024,1024,11,9,1952619.00,1952132.27,21.55
+gcd,kernel,1024,1024,11,9,1984084.73,1978308.55,21.82
+gcd,gmp,1024,1024,24,9,866601.00,865159.29,20.80
+gcd,gmpz,1024,1024,23,9,889328.26,888183.70,20.46
+gcd,auto,2000,2000,4,9,6272448.00,6264704.75,25.10
+gcd,kernel,2000,2000,4,9,6281446.00,6272586.75,25.13
+gcd,gmp,2000,2000,9,9,2302021.44,2301419.11,20.72
+gcd,gmpz,2000,2000,9,9,2306932.44,2304292.89,20.78
+gcd,auto,16384,16384,1,3,404620180.00,403945919.00,406.03
+gcd,kernel,16384,16384,1,3,404808313.00,404722180.00,404.92
+gcd,gmp,16384,16384,1,3,44050540.00,44031286.00,44.05
+gcd,gmpz,16384,16384,1,3,44024204.00,44023175.00,44.03
+gcd,auto,65536,65536,1,1,6391869486.00,6391869486.00,6391.87
+gcd,kernel,65536,65536,1,1,6399550336.00,6399550336.00,6399.55
+gcd,gmp,65536,65536,1,1,261431132.00,261431132.00,261.43
+gcd,gmpz,65536,65536,1,1,260899213.00,260899213.00,260.90
diff --git a/tests/beman/big_int/perf/gmp_gap_x64_gcc14_noifma.csv b/tests/beman/big_int/perf/gmp_gap_x64_gcc14_noifma.csv
new file mode 100644
index 00000000..a5649312
--- /dev/null
+++ b/tests/beman/big_int/perf/gmp_gap_x64_gcc14_noifma.csv
@@ -0,0 +1,123 @@
+# gap_sweep date 2026-10-06T14:08:18Z host mborland-MS-7C75 Linux x86_64
+# gap_sweep bin builds/gcc14-noifma/shape_sweep seed=1 ops=mul,sqr bands=small,medium,large pin=taskset -c 2
+#const square_long_cutoff=10 karatsuba_cutoff=47 karatsuba_fallback=40 square_karatsuba_cutoff=120 toom_cook_3_cutoff=400 toom_cook_4_cutoff=1600 toom_cook_6_5_cutoff=1800 toom_cook_8_5_cutoff=7000 square_toom_cook_3_cutoff=440 square_toom_cook_4_cutoff=2000 square_toom_cook_6_5_cutoff=2000 square_toom_cook_8_5_cutoff=5000 fft_mul_min_limbs=6000 fft_mul_model_num=1 fft_mul_model_den=0 square_fft_min_limbs=11000 square_fft_model_num=1 square_fft_model_den=0 fft_model_log_power=1 fft_model_root_degree=2 fft_cyclic_cutoff=8192 multiply_mod_bnm1_cutoff=16 mul_slice_z0_min=47 mul_slice_z0_num=7 mul_slice_z0_den=4 mul_slice_z1_min=400 mul_slice_z1_num=3 mul_slice_z1_den=2 mul_slice_z2_min=16000 mul_slice_z2_num=9 mul_slice_z2_den=8 mul_slice_z3_min=0 mul_slice_z3_num=3 mul_slice_z3_den=2 mul_slice_z4_min=0 mul_slice_z4_num=3 mul_slice_z4_den=2 mul_slice_z5_min=0 mul_slice_z5_num=3 mul_slice_z5_den=2 mul_slice_zone_count=3 ifma_multiply_min_limbs=6 ifma_multiply_mid_limbs=8 ifma_multiply_high_limbs=13 ifma_multiply_always_limbs=20 ifma_multiply_min_product_low=900 ifma_multiply_min_product_mid=450 ifma_multiply_min_product_high=330 ifma_square_min_limbs=16 ifma_square_native_max_limbs=256 burnikel_ziegler_cutoff=160 burnikel_ziegler_offset=64 barrett_march_cutoff=512 barrett_march8_cutoff=4096 barrett_quarter_cutoff=49152 barrett_balanced_cutoff=196608 reciprocal_span_cutoff=512 fast_input_basecase_chunks=512 fast_output_basecase_chunks=16 fast_input_charconv_min_chunks=8 fast_output_preinv_min_limbs=98304 ARCH=x86_64 BMI2_ADX=1 AVX512_IFMA=0 SIMD_MUL=1 limb_bits=64 ndebug=1 optimize=1 isa_bmi2=1 isa_adx=1 isa_avx2=1 isa_avx512f=1 isa_avx512ifma=1 compiler=gcc-14.3.0 via=cmake build_type=RelWithDebInfo cxx_flags=-march=native_-O3__-O2_-g_-DNDEBUG_defs:
+op,path,la,lb,reps,rounds,median_ns,min_ns,round_ms_actual
+mul,auto,1,1,1044821,9,18.91,18.84,19.75
+mul,gmp,1,1,5865789,9,3.40,3.38,19.92
+mul,gmpz,1,1,5042194,9,3.97,3.97,20.02
+mul,auto,2,2,736030,9,27.47,27.37,20.23
+mul,kernel,2,2,2912230,9,7.36,7.35,21.46
+mul,gmp,2,2,5071131,9,3.94,3.93,19.99
+mul,gmpz,2,2,3327291,9,6.01,5.98,20.03
+mul,auto,3,3,685406,9,29.18,28.98,20.04
+mul,kernel,3,3,2452669,9,8.76,8.70,21.47
+mul,gmp,3,3,2453551,9,8.11,8.02,19.86
+mul,gmpz,3,3,2045415,9,9.81,9.77,20.08
+mul,auto,4,4,637390,9,31.53,31.18,20.48
+mul,kernel,4,4,1872223,9,11.57,11.46,21.72
+mul,gmp,4,4,2102620,9,9.52,9.48,20.02
+mul,gmpz,4,4,1667780,9,11.92,11.89,19.89
+mul,auto,6,6,519458,9,38.57,38.50,20.06
+mul,kernel,6,6,1268107,9,16.26,16.18,20.61
+mul,gmp,6,6,1277997,9,15.66,15.62,20.01
+mul,gmpz,6,6,1112883,9,17.89,17.86,19.91
+mul,auto,8,8,439559,9,45.46,45.28,20.00
+mul,kernel,8,8,852917,9,23.88,23.86,20.38
+mul,gmp,8,8,732042,9,27.16,26.95,19.87
+mul,gmpz,8,8,686284,9,28.82,28.24,19.78
+mul,auto,12,12,293956,9,67.98,67.94,19.99
+mul,kernel,12,12,429636,9,47.15,47.12,20.29
+mul,gmp,12,12,411979,9,48.56,48.54,20.02
+mul,gmpz,12,12,384110,9,52.00,51.99,19.98
+mul,auto,16,16,201308,9,101.20,99.46,20.30
+mul,kernel,16,16,249875,9,77.02,76.92,19.34
+mul,gmp,16,16,244268,9,81.90,81.88,20.01
+mul,gmpz,16,16,237919,9,84.07,84.02,20.01
+mul,auto,32,32,57131,9,342.54,342.41,19.58
+mul,kernel,32,32,63241,9,317.09,316.10,20.09
+mul,gmp,32,32,73919,9,270.62,270.59,20.01
+mul,gmpz,32,32,73792,9,271.04,270.98,20.02
+mul,auto,64,64,16677,9,1198.28,1197.59,19.99
+mul,kernel,64,64,17238,9,1161.93,1161.14,20.03
+mul,gmp,64,64,23284,9,859.06,858.73,20.00
+mul,gmpz,64,64,23218,9,861.40,861.03,20.00
+mul,auto,128,128,5077,9,3936.90,3935.23,20.00
+mul,kernel,128,128,5173,9,3868.95,3867.79,20.02
+mul,gmp,128,128,7384,9,2708.52,2708.31,20.00
+mul,gmpz,128,128,7333,9,2725.91,2724.00,19.99
+mul,auto,256,256,1606,9,12460.78,12457.20,20.02
+mul,kernel,256,256,1617,9,12380.34,12371.04,20.05
+mul,gmp,256,256,2561,9,7809.90,7808.16,20.00
+mul,gmpz,256,256,2563,9,7792.96,7789.69,19.97
+mul,auto,512,512,523,9,38254.68,38248.63,20.01
+mul,kernel,512,512,525,9,38102.05,38083.35,20.00
+mul,gmp,512,512,960,9,20828.44,20817.66,20.00
+mul,gmpz,512,512,958,9,20877.61,20865.48,20.00
+mul,auto,1000,1000,183,9,109822.43,109772.50,20.11
+mul,kernel,1000,1000,184,9,109266.71,109214.29,20.10
+mul,gmp,1000,1000,376,9,53190.94,53180.17,20.00
+mul,gmpz,1000,1000,376,9,53300.41,53277.51,20.04
+mul,auto,2000,2000,68,9,295675.00,295458.07,20.10
+mul,kernel,2000,2000,68,9,295837.68,295744.34,20.13
+mul,gmp,2000,2000,139,9,144001.27,143967.72,20.02
+mul,gmpz,2000,2000,139,9,144012.88,143968.25,20.02
+mul,auto,2000,100,389,9,51527.55,51499.22,20.04
+mul,kernel,2000,100,300,9,66771.50,66758.82,20.03
+mul,gmp,2000,100,558,9,35798.75,35746.84,19.99
+mul,gmpz,2000,100,558,9,35920.20,35823.20,20.03
+mul,auto,4096,4096,24,3,847402.29,846871.62,20.34
+mul,kernel,4096,4096,24,3,845874.33,845435.42,20.30
+mul,gmp,4096,4096,53,3,383889.75,383888.51,20.35
+mul,gmpz,4096,4096,53,3,383734.32,383730.11,20.34
+mul,auto,16384,16384,3,3,7592651.00,7581283.00,22.79
+mul,kernel,16384,16384,3,3,7715168.00,7712772.00,23.15
+mul,gmp,16384,16384,12,3,1781673.92,1768321.17,21.42
+mul,gmpz,16384,16384,12,3,1768836.67,1767971.42,21.22
+mul,auto,65536,65536,1,3,32089233.00,32066157.00,32.09
+mul,kernel,65536,65536,1,3,31552502.00,31520756.00,31.56
+mul,gmp,65536,65536,3,3,8629049.33,8618348.67,25.89
+mul,gmpz,65536,65536,3,3,8638365.67,8629756.33,25.91
+mul,auto,262144,262144,1,3,138380709.00,138379149.00,138.39
+mul,kernel,262144,262144,1,3,135928417.00,135889795.00,135.94
+mul,gmp,262144,262144,1,3,49504353.00,49500731.00,49.51
+mul,gmpz,262144,262144,1,3,49552054.00,49547390.00,49.58
+mul,auto,1048576,1048576,1,3,750420185.00,749769937.00,750.25
+mul,kernel,1048576,1048576,1,3,749946014.00,746789765.00,748.97
+mul,gmp,1048576,1048576,1,3,246970200.00,246742959.00,246.99
+mul,gmpz,1048576,1048576,1,3,246857795.00,246781277.00,246.84
+mul,auto,262144,8192,1,3,66046255.00,66039422.00,66.07
+mul,kernel,262144,8192,1,3,65770463.00,65759102.00,65.78
+mul,gmp,262144,8192,2,3,19122921.50,19119336.00,38.25
+mul,gmpz,262144,8192,2,3,19138175.50,19135640.50,38.28
+sqr,auto,4,4,622521,9,32.30,32.23,20.13
+sqr,kernel,4,4,2231728,9,8.99,8.96,20.07
+sqr,gmp,4,4,3467964,9,5.77,5.72,19.99
+sqr,gmpz,4,4,2316128,9,8.66,8.62,20.10
+sqr,auto,16,16,232439,9,85.99,85.89,20.00
+sqr,kernel,16,16,326724,9,61.26,61.20,20.02
+sqr,gmp,16,16,401405,9,49.81,49.80,20.01
+sqr,gmpz,16,16,384023,9,52.09,52.06,20.00
+sqr,auto,64,64,27309,9,724.47,719.19,19.80
+sqr,kernel,64,64,29247,9,683.92,683.75,20.00
+sqr,gmp,64,64,36408,9,549.47,549.17,20.01
+sqr,gmpz,64,64,36120,9,552.54,552.11,19.96
+sqr,auto,512,512,769,9,26041.90,26029.52,20.04
+sqr,kernel,512,512,772,9,25922.78,25906.98,20.01
+sqr,gmp,512,512,1323,9,15126.46,15119.89,20.02
+sqr,gmpz,512,512,1319,9,15170.26,15158.25,20.02
+sqr,auto,2000,2000,97,9,207933.43,207856.13,20.17
+sqr,kernel,2000,2000,97,9,206983.52,206795.40,20.09
+sqr,gmp,2000,2000,199,9,100903.49,100852.84,20.10
+sqr,gmpz,2000,2000,199,9,100892.29,100878.85,20.08
+sqr,auto,16384,16384,3,3,7180432.67,7166699.33,21.54
+sqr,kernel,16384,16384,3,3,7091666.00,7086779.00,21.30
+sqr,gmp,16384,16384,18,3,1149116.89,1148863.78,20.68
+sqr,gmpz,16384,16384,18,3,1148658.56,1148654.83,20.68
+sqr,auto,262144,262144,1,3,123695315.00,123449033.00,123.65
+sqr,kernel,262144,262144,1,3,116647509.00,116625430.00,116.71
+sqr,gmp,262144,262144,1,3,32821163.00,32817473.00,32.84
+sqr,gmpz,262144,262144,1,3,32816510.00,32775240.00,32.80
+sqr,auto,1048576,1048576,1,3,598163346.00,598089014.00,601.17
+sqr,kernel,1048576,1048576,1,3,596473015.00,595490528.00,596.54
+sqr,gmp,1048576,1048576,1,3,165028814.00,164863129.00,165.02
+sqr,gmpz,1048576,1048576,1,3,165131159.00,165035741.00,165.12
diff --git a/tests/beman/big_int/perf/shape_sweep.cpp b/tests/beman/big_int/perf/shape_sweep.cpp
index 0315e6f4..24c23cfd 100644
--- a/tests/beman/big_int/perf/shape_sweep.cpp
+++ b/tests/beman/big_int/perf/shape_sweep.cpp
@@ -1,21 +1,37 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-License-Identifier: BSL-1.0
-// End-to-end shape sweep for tuning: times multiplication, squaring and division through the public big_int
-// operators (so the real dispatcher runs) over a list of operand shapes, and prints CSV on stdout. Every other line
-// starts with '#'; the first is a '#const' line listing every tuning constant and the build configuration, which the
-// scratch drivers (build/tune/bin/variant.sh, abab.sh) use to detect stale or mismatched binaries.
+// End-to-end shape sweep for tuning and for the GMP gap analysis: times big_int operations over a list of operand
+// shapes and prints CSV on stdout. Every other line starts with '#'; the first is a '#const' line listing every tuning
+// constant and the build configuration, which the scratch drivers (build/tune/bin/variant.sh, abab.sh,
+// build/gmpgap/bin/gap_sweep.sh) use to detect stale or mismatched binaries.
//
-// shape_sweep [--path auto|sliced|unsliced|fft] [--rounds N] [--round-ms X] [--seed S]
-// [--gmp] [--grid FILE] [AxB ...]
+// shape_sweep [--rows R1,R2,...] [--path R] [--rounds N] [--round-ms X] [--seed S] [--gmp] [--grid FILE]
+// [SHAPE ...]
//
-// Shapes are limb counts: mul is la x lb, div/rem/divrem is dividend x divisor, sqr is N or NxN. --path
-// sliced|unsliced|fft (mul only) calls the runtime kernels directly on raw limb spans; sliced and unsliced need the
-// shape-aware dispatch (BEMAN_BIG_INT_HAS_SHAPE_DISPATCH), fft needs 64-bit limbs and, like the dispatcher, zeroes
-// the result and allocates its workspaces inside the timed region. Every shape is checked against residues modulo
-// two primes before it is timed and again on the last product of the timed loop. --gmp needs
-// -DBEMAN_BIG_INT_SWEEP_GMP, 64-bit limbs and -lgmp; div and rem time only the named operator, so GMP's mpn_tdiv_qr
-// (quotient and remainder) is paired with divrem only.
+// ops and shapes (limb counts unless noted):
+// add sub cmp gcd AxB (cmp: N or NxN, operands equal except limb 0; gcd: b is made odd for every row)
+// shl shr NxS: N limbs shifted by S bits
+// mul AxB sqr N or NxN div rem divrem dividend x divisor
+// tochars NxBASE fromchars NxBASE (the decimal-or-other string of a random N-limb value)
+//
+// Rows (CSV column 'path'; default 'auto'; rows of one shape run back to back):
+// auto the public big_int API into a pre-existing destination (c = a + b, ...)
+// inplace add/sub: c += b; c -= b, shl/shr: c <<= s; c >>= s. The pair is timed and the reported per-op time is
+// half. kernel the library's detail-level span kernels on preallocated buffers; for mul/sqr it is the runtime tier
+// ladder
+// (unsliced) and shares its timings with --rows unsliced. Kernels that work in place (shifts, gcd, tochars
+// and gmp's get_str/gcd/set_str inputs) include the copy-in of their operands in the timed loop.
+// kernelip shl/shr only: in-place pair shl then shr on one buffer, halved, no copy-in
+// gmp GMP mpn on preallocated buffers (needs -DBEMAN_BIG_INT_SWEEP_GMP, 64-bit limbs, -lgmp)
+// gmpz GMP mpz with the destination reused, operands loaded with mpz_import
+// sliced unsliced fft (mul only) the runtime kernels called directly on raw limb spans; sliced and unsliced need
+// BEMAN_BIG_INT_HAS_SHAPE_DISPATCH, fft needs 64-bit limbs and, like the dispatcher, zeroes the result and
+// allocates its workspaces inside the timed region.
+// --path X is an alias for --rows X and --gmp appends the gmp row. A row that does not apply to an op or shape is
+// reported on a '# skip' line. div and rem time only the named operator, so the kernel and GMP rows exist for divrem
+// only. Every row is checked (residue identities, identical limbs / sign / digits, gcd against the divisibility and
+// mpz_gcd) before it is timed and again after.
//
// Build through CMake (target beman.big_int.benchmarks.shape_sweep, BEMAN_BIG_INT_BUILD_BENCHMARKS=ON), which passes
// the library's PUBLIC definitions (BMI2_ADX, AVX512_IFMA, SIMD_MUL, FORCED_LIMB_WIDTH) and the compile flags. A
@@ -42,11 +58,13 @@
#include
#include
+#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include
@@ -62,14 +80,44 @@
#if defined(BEMAN_BIG_INT_SWEEP_GMP)
static_assert(sizeof(unsigned long) == 8, "the GMP column assumes a 64-bit mp_limb_t");
-// The x86 box has libgmp but no gmp.h, so declare the few mpn entry points ourselves.
+// The x86 box has libgmp but no gmp.h, so declare the few mpn/mpz entry points ourselves.
extern "C" {
using mp_limb_t = unsigned long;
+struct __mpz_struct {
+ int _mp_alloc;
+ int _mp_size;
+ mp_limb_t* _mp_d;
+};
extern const char* const __gmp_version;
void __gmpn_mul(mp_limb_t* rp, const mp_limb_t* up, long un, const mp_limb_t* vp, long vn);
void __gmpn_sqr(mp_limb_t* rp, const mp_limb_t* up, long n);
void __gmpn_tdiv_qr(
mp_limb_t* qp, mp_limb_t* rp, long qxn, const mp_limb_t* np, long nn, const mp_limb_t* dp, long dn);
+mp_limb_t __gmpn_add_n(mp_limb_t* rp, const mp_limb_t* up, const mp_limb_t* vp, long n);
+mp_limb_t __gmpn_add(mp_limb_t* rp, const mp_limb_t* up, long un, const mp_limb_t* vp, long vn);
+mp_limb_t __gmpn_sub_n(mp_limb_t* rp, const mp_limb_t* up, const mp_limb_t* vp, long n);
+mp_limb_t __gmpn_sub(mp_limb_t* rp, const mp_limb_t* up, long un, const mp_limb_t* vp, long vn);
+mp_limb_t __gmpn_lshift(mp_limb_t* rp, const mp_limb_t* up, long n, unsigned cnt);
+mp_limb_t __gmpn_rshift(mp_limb_t* rp, const mp_limb_t* up, long n, unsigned cnt);
+int __gmpn_cmp(const mp_limb_t* up, const mp_limb_t* vp, long n);
+mp_limb_t __gmpn_divrem_1(mp_limb_t* qp, long qxn, const mp_limb_t* up, long un, mp_limb_t d);
+std::size_t __gmpn_get_str(unsigned char* str, int base, mp_limb_t* up, long un);
+long __gmpn_set_str(mp_limb_t* rp, const unsigned char* str, std::size_t len, int base);
+long __gmpn_gcd(mp_limb_t* rp, mp_limb_t* up, long un, mp_limb_t* vp, long vn);
+void __gmpz_init(__mpz_struct* z);
+void __gmpz_clear(__mpz_struct* z);
+void __gmpz_import(
+ __mpz_struct* z, std::size_t count, int order, std::size_t size, int endian, std::size_t nails, const void* data);
+void __gmpz_add(__mpz_struct* r, const __mpz_struct* a, const __mpz_struct* b);
+void __gmpz_sub(__mpz_struct* r, const __mpz_struct* a, const __mpz_struct* b);
+void __gmpz_mul(__mpz_struct* r, const __mpz_struct* a, const __mpz_struct* b);
+void __gmpz_mul_2exp(__mpz_struct* r, const __mpz_struct* a, unsigned long bits);
+void __gmpz_tdiv_q_2exp(__mpz_struct* r, const __mpz_struct* a, unsigned long bits);
+void __gmpz_tdiv_qr(__mpz_struct* q, __mpz_struct* r, const __mpz_struct* n, const __mpz_struct* d);
+void __gmpz_gcd(__mpz_struct* g, const __mpz_struct* a, const __mpz_struct* b);
+int __gmpz_cmp(const __mpz_struct* a, const __mpz_struct* b);
+char* __gmpz_get_str(char* str, int base, const __mpz_struct* z);
+int __gmpz_set_str(__mpz_struct* z, const char* str, int base);
}
#endif
@@ -340,7 +388,7 @@ using clock_type = std::chrono::steady_clock;
struct options {
std::string op;
- std::string path = "auto";
+ std::vector rows{"auto"};
unsigned rounds{9};
double round_ms{20.0};
u64 seed{1};
@@ -506,156 +554,307 @@ std::size_t trimmed_size(const std::span x) {
return n;
}
-#if defined(BEMAN_BIG_INT_SWEEP_GMP)
-// GMP timing shared by the ops; check(gr) validates the last result.
+using limbs = std::vector;
+
+// Trimmed copy of x (a zero value is the single limb 0, whatever its representation).
+limbs trimmed(const std::span x) {
+ const std::size_t n = x.empty() ? 0 : trimmed_size(x);
+ limbs v(x.begin(), x.begin() + static_cast(n));
+ if (v.empty()) {
+ v.push_back(0);
+ }
+ return v;
+}
+
+void expect_same(const std::string& what,
+ const std::string& shape,
+ const std::span got,
+ const limbs& ref) {
+ if (trimmed(got) != ref) {
+ fail(what + " result differs from the reference at " + shape);
+ }
+}
+
+u64 pow2mod(u64 e, const u64 p) {
+ u64 r = 1;
+ u64 b = 2 % p;
+ for (; e != 0; e >>= 1) {
+ if ((e & 1) != 0) {
+ r = mulmod(r, b, p);
+ }
+ b = mulmod(b, b, p);
+ }
+ return r;
+}
+
+bool has_row(const options& opt, const char* row) { return std::ranges::find(opt.rows, row) != opt.rows.end(); }
+
+void skip_row(const options& opt, const std::string& row, const std::string& why) {
+ std::printf("# skip %s %s: %s\n", opt.op.c_str(), row.c_str(), why.c_str());
+ std::fflush(stdout);
+}
+
+// Times one row: untimed check, the timed loop, the check again. `scale` converts a timed unit of work that holds
+// several operations into per-operation time (0.5 for the add/sub and shift pairs).
template
-void time_gmp(const options& opt, const std::size_t la, const std::size_t lb, Work&& work, Check&& check) {
+void time_row(const options& opt,
+ const char* name,
+ const std::size_t la,
+ const std::size_t lb,
+ Work&& work,
+ Check&& check,
+ const double scale = 1.0) {
work();
check();
- const timing t = measure(work, opt);
+ timing t = measure(work, opt);
check();
- print_row(opt, "gmp", la, lb, t);
+ t.median_ns *= scale;
+ t.min_ns *= scale;
+ print_row(opt, name, la, lb, t);
+}
+
+#if defined(BEMAN_BIG_INT_SWEEP_GMP)
+// GMP mpz operand/destination with the limbs readable through the hand-declared struct.
+struct zint {
+ __mpz_struct z{};
+
+ zint() { __gmpz_init(&z); }
+ explicit zint(const std::span v) : zint() {
+ __gmpz_import(&z, v.size(), -1, sizeof(limb_t), 0, 0, v.data());
+ }
+ ~zint() { __gmpz_clear(&z); }
+ zint(const zint&) = delete;
+ zint& operator=(const zint&) = delete;
+
+ [[nodiscard]] limbs get() const {
+ const std::size_t n = static_cast(z._mp_size < 0 ? -z._mp_size : z._mp_size);
+ return trimmed(limbs(z._mp_d, z._mp_d + n));
+ }
+};
+
+std::vector to_gmp(const std::span x) { return {x.begin(), x.end()}; }
+limbs from_gmp(const std::vector& x, const std::size_t n) {
+ return limbs(x.begin(), x.begin() + static_cast(n));
}
#endif
+// ---------------------------------------------------------------------------
+// mul and sqr.
+// ---------------------------------------------------------------------------
+
void run_mul(const options& opt, const std::size_t la, const std::size_t lb) {
std::mt19937_64 rng(splitmix(opt.seed ^ splitmix(la * 1000003 + lb)));
const auto va = random_limbs(rng, la);
const auto vb = random_limbs(rng, lb);
-
- if (opt.path == "auto") {
- const bb::big_int a = make_big(va);
- const bb::big_int b = make_big(vb);
- {
- const bb::big_int c = a * b;
- check_product("mul", la, lb, va, vb, c.representation());
- }
- bb::big_int c;
- auto work = [&] {
- c = a * b;
- escape(&c);
- escape(c.representation().data());
- };
- const timing t = measure(work, opt);
- check_product("mul (last timed product)", la, lb, va, vb, c.representation());
- print_row(opt, "auto", la, lb, t);
- } else if (opt.path == "sliced" || opt.path == "unsliced") {
- if (la < 2 || lb < 2) {
- usage_error("--path " + opt.path + " needs both operands of at least 2 limbs");
- }
- const path_fn fn = select_runtime_path(opt.path);
- std::allocator alloc;
- const dt::scratch_allocator> hooks(alloc);
- std::vector result(la + lb);
- std::size_t n = 0;
- auto work = [&] {
- std::ranges::fill(result, limb_t{0});
- n = fn(result, va, vb, hooks.heap());
- escape(&n);
- escape(result.data());
- };
- const auto verify = [&](const char* what) {
- check_product(what, la, lb, va, vb, result);
- if (n != trimmed_size(result)) {
- fail(opt.path + " returned size " + std::to_string(n) + " but the trimmed size is " +
- std::to_string(trimmed_size(result)) + " at " + shape_str(la, lb));
+ const auto sh = shape_str(la, lb);
+
+ for (const auto& row : opt.rows) {
+ if (row == "auto") {
+ const bb::big_int a = make_big(va);
+ const bb::big_int b = make_big(vb);
+ {
+ const bb::big_int c = a * b;
+ check_product("mul", la, lb, va, vb, c.representation());
}
- };
- work();
- verify(opt.path.c_str());
- const timing t = measure(work, opt);
- verify((opt.path + " (last timed product)").c_str());
- print_row(opt, opt.path.c_str(), la, lb, t);
- } else if (opt.path == "fft") {
- if constexpr (limb_bits != 64) {
- usage_error("--path fft needs 64-bit limbs");
- } else {
- // Like mul_dispatch.cpp: zeroed result, workspaces from the heap hooks, inside the timed region.
+ bb::big_int c;
+ auto work = [&] {
+ c = a * b;
+ escape(&c);
+ escape(c.representation().data());
+ };
+ const timing t = measure(work, opt);
+ check_product("mul (last timed product)", la, lb, va, vb, c.representation());
+ print_row(opt, "auto", la, lb, t);
+ } else if (row == "sliced" || row == "unsliced" || row == "kernel") {
+ const std::string path = row == "kernel" ? "unsliced" : row;
+ if (la < 2 || lb < 2) {
+ if (row == "kernel") {
+ skip_row(opt, row, "the tier ladder needs both operands of at least 2 limbs");
+ continue;
+ }
+ usage_error("--path " + row + " needs both operands of at least 2 limbs");
+ }
+#if !defined(BEMAN_BIG_INT_HAS_SHAPE_DISPATCH)
+ if (row == "kernel") {
+ skip_row(opt, row, "needs BEMAN_BIG_INT_HAS_SHAPE_DISPATCH");
+ continue;
+ }
+#endif
+ const path_fn fn = select_runtime_path(path);
std::allocator alloc;
const dt::scratch_allocator> hooks(alloc);
std::vector result(la + lb);
+ std::size_t n = 0;
auto work = [&] {
std::ranges::fill(result, limb_t{0});
-#if defined(BEMAN_BIG_INT_SIMD_MUL)
- dt::scratch_heap_array fp_ws(hooks.heap(), dt::fft_mul_fp_storage_size(la, lb));
- dt::scratch_heap_array int_ws(hooks.heap(), dt::fft_mul_int_storage_size(la, lb));
- dt::multiply_fft(result, va, vb, fp_ws.span(), int_ws.span());
-#else
- dt::scratch_heap_array ws(hooks.heap(), dt::fft_mul_storage_size(la, lb));
- dt::multiply_fft(result, va, vb, ws.span());
-#endif
+ n = fn(result, va, vb, hooks.heap());
+ escape(&n);
escape(result.data());
};
+ const auto verify = [&](const char* what) {
+ check_product(what, la, lb, va, vb, result);
+ if (n != trimmed_size(result)) {
+ fail(path + " returned size " + std::to_string(n) + " but the trimmed size is " +
+ std::to_string(trimmed_size(result)) + " at " + sh);
+ }
+ };
work();
- check_product("fft", la, lb, va, vb, result);
+ verify(path.c_str());
const timing t = measure(work, opt);
- check_product("fft (last timed product)", la, lb, va, vb, result);
- print_row(opt, "fft", la, lb, t);
- }
- } else {
- usage_error("unknown --path " + opt.path);
- }
-
+ verify((path + " (last timed product)").c_str());
+ print_row(opt, row.c_str(), la, lb, t);
+ } else if (row == "fft") {
+ if constexpr (limb_bits != 64) {
+ usage_error("--path fft needs 64-bit limbs");
+ } else {
+ // Like mul_dispatch.cpp: zeroed result, workspaces from the heap hooks, inside the timed region.
+ std::allocator alloc;
+ const dt::scratch_allocator> hooks(alloc);
+ std::vector result(la + lb);
+ auto work = [&] {
+ std::ranges::fill(result, limb_t{0});
+#if defined(BEMAN_BIG_INT_SIMD_MUL)
+ dt::scratch_heap_array fp_ws(hooks.heap(), dt::fft_mul_fp_storage_size(la, lb));
+ dt::scratch_heap_array int_ws(hooks.heap(), dt::fft_mul_int_storage_size(la, lb));
+ dt::multiply_fft(result, va, vb, fp_ws.span(), int_ws.span());
+#else
+ dt::scratch_heap_array ws(hooks.heap(), dt::fft_mul_storage_size(la, lb));
+ dt::multiply_fft(result, va, vb, ws.span());
+#endif
+ escape(result.data());
+ };
+ work();
+ check_product("fft", la, lb, va, vb, result);
+ const timing t = measure(work, opt);
+ check_product("fft (last timed product)", la, lb, va, vb, result);
+ print_row(opt, "fft", la, lb, t);
+ }
#if defined(BEMAN_BIG_INT_SWEEP_GMP)
- if (opt.gmp) {
- const std::vector ga(va.begin(), va.end());
- const std::vector gb(vb.begin(), vb.end());
- std::vector gr(la + lb);
- time_gmp(
- opt,
- la,
- lb,
- [&] {
- if (la >= lb) {
- __gmpn_mul(gr.data(), ga.data(), static_cast(la), gb.data(), static_cast(lb));
- } else {
- __gmpn_mul(gr.data(), gb.data(), static_cast(lb), ga.data(), static_cast(la));
- }
- escape(gr.data());
- },
- [&] { check_product("gmp mul", la, lb, va, vb, std::vector(gr.begin(), gr.end())); });
- }
+ } else if (row == "gmp") {
+ const auto ga = to_gmp(va);
+ const auto gb = to_gmp(vb);
+ std::vector gr(la + lb);
+ time_row(
+ opt,
+ "gmp",
+ la,
+ lb,
+ [&] {
+ if (la >= lb) {
+ __gmpn_mul(gr.data(), ga.data(), static_cast(la), gb.data(), static_cast(lb));
+ } else {
+ __gmpn_mul(gr.data(), gb.data(), static_cast(lb), ga.data(), static_cast(la));
+ }
+ escape(gr.data());
+ },
+ [&] { check_product("gmp mul", la, lb, va, vb, from_gmp(gr, gr.size())); });
+ } else if (row == "gmpz") {
+ const zint za(va);
+ const zint zb(vb);
+ zint zc;
+ time_row(
+ opt,
+ "gmpz",
+ la,
+ lb,
+ [&] {
+ __gmpz_mul(&zc.z, &za.z, &zb.z);
+ escape(zc.z._mp_d);
+ },
+ [&] { check_product("gmpz mul", la, lb, va, vb, zc.get()); });
#endif
+ } else {
+ skip_row(opt, row, "not applicable");
+ }
+ }
}
void run_sqr(const options& opt, const std::size_t n) {
std::mt19937_64 rng(splitmix(opt.seed ^ splitmix(n * 1000003 + n)));
const auto va = random_limbs(rng, n);
- const bb::big_int a = make_big(va);
- {
- const bb::big_int c = a * a; // same object on both sides selects the squaring path
- check_product("sqr", n, n, va, va, c.representation());
- }
- bb::big_int c;
- auto work = [&] {
- c = a * a;
- escape(&c);
- escape(c.representation().data());
- };
- const timing t = measure(work, opt);
- check_product("sqr (last timed product)", n, n, va, va, c.representation());
- print_row(opt, "auto", n, n, t);
-
+ for (const auto& row : opt.rows) {
+ if (row == "auto") {
+ const bb::big_int a = make_big(va);
+ {
+ const bb::big_int c = a * a; // same object on both sides selects the squaring path
+ check_product("sqr", n, n, va, va, c.representation());
+ }
+ bb::big_int c;
+ auto work = [&] {
+ c = a * a;
+ escape(&c);
+ escape(c.representation().data());
+ };
+ const timing t = measure(work, opt);
+ check_product("sqr (last timed product)", n, n, va, va, c.representation());
+ print_row(opt, "auto", n, n, t);
+ } else if (row == "kernel") {
+ if (n < 2) {
+ skip_row(opt, row, "the squaring ladder needs at least 2 limbs");
+ continue;
+ }
+ // dt::square_runtime directly (the mul kernel row's squaring counterpart); result zeroed inside the loop.
+ std::allocator alloc;
+ const dt::scratch_allocator> hooks(alloc);
+ std::vector result(2 * n);
+ std::size_t size = 0;
+ time_row(
+ opt,
+ "kernel",
+ n,
+ n,
+ [&] {
+ std::ranges::fill(result, limb_t{0});
+ size = dt::square_runtime(result, va, hooks.heap());
+ escape(&size);
+ escape(result.data());
+ },
+ [&] {
+ check_product("kernel sqr", n, n, va, va, result);
+ if (size != trimmed_size(result)) {
+ fail("kernel sqr returned a wrong size at " + shape_str(n, n));
+ }
+ });
#if defined(BEMAN_BIG_INT_SWEEP_GMP)
- if (opt.gmp) {
- const std::vector ga(va.begin(), va.end());
- std::vector gr(2 * n);
- time_gmp(
- opt,
- n,
- n,
- [&] {
- __gmpn_sqr(gr.data(), ga.data(), static_cast(n));
- escape(gr.data());
- },
- [&] { check_product("gmp sqr", n, n, va, va, std::vector(gr.begin(), gr.end())); });
- }
+ } else if (row == "gmp") {
+ const auto ga = to_gmp(va);
+ std::vector gr(2 * n);
+ time_row(
+ opt,
+ "gmp",
+ n,
+ n,
+ [&] {
+ __gmpn_sqr(gr.data(), ga.data(), static_cast(n));
+ escape(gr.data());
+ },
+ [&] { check_product("gmp sqr", n, n, va, va, from_gmp(gr, gr.size())); });
+ } else if (row == "gmpz") {
+ const zint za(va);
+ zint zc;
+ time_row(
+ opt,
+ "gmpz",
+ n,
+ n,
+ [&] {
+ __gmpz_mul(&zc.z, &za.z, &za.z); // same operand selects mpn_sqr
+ escape(zc.z._mp_d);
+ },
+ [&] { check_product("gmpz sqr", n, n, va, va, zc.get()); });
#endif
+ } else {
+ skip_row(opt, row, "not applicable");
+ }
+ }
}
-// div, rem and divrem: each times only its own operator; the other half of the pair is computed untimed so both
-// are always checked.
+// ---------------------------------------------------------------------------
+// div, rem and divrem: div and rem time only their own operator (auto row only); the other half of the pair is
+// computed untimed so both are always checked. The kernel and GMP rows time the combined quotient and remainder.
+// ---------------------------------------------------------------------------
+
void run_div(const options& opt, const std::size_t la, const std::size_t lb) {
if (la < lb) {
usage_error(opt.op + " needs dividend limbs >= divisor limbs, got " + shape_str(la, lb));
@@ -663,78 +862,1026 @@ void run_div(const options& opt, const std::size_t la, const std::size_t lb) {
std::mt19937_64 rng(splitmix(opt.seed ^ splitmix(la * 1000003 + lb)));
const auto va = random_limbs(rng, la);
const auto vb = random_limbs(rng, lb);
+ const auto sh = shape_str(la, lb);
const bb::big_int a = make_big(va);
const bb::big_int b = make_big(vb);
+ limbs ref_q;
+ limbs ref_r;
{
const bb::big_int q = a / b;
const bb::big_int r = a % b;
check_division(opt.op.c_str(), la, lb, va, vb, q, r, b);
+ ref_q = trimmed(q.representation());
+ ref_r = trimmed(r.representation());
+ }
+ const bool is_divrem = opt.op == "divrem";
+
+ for (const auto& row : opt.rows) {
+ if (row == "auto") {
+ bb::big_int q;
+ bb::big_int r;
+ auto work = [&] {
+ if (opt.op == "div") {
+ q = a / b;
+ escape(&q);
+ escape(q.representation().data());
+ } else if (opt.op == "rem") {
+ r = a % b;
+ escape(&r);
+ escape(r.representation().data());
+ } else {
+ auto qr = bb::div_rem_to_zero(a, b);
+ q = std::move(qr.quotient);
+ r = std::move(qr.remainder);
+ escape(&q);
+ escape(&r);
+ escape(q.representation().data());
+ }
+ };
+ const timing t = measure(work, opt);
+ if (opt.op == "div") {
+ r = a % b;
+ } else if (opt.op == "rem") {
+ q = a / b;
+ }
+ check_division((opt.op + " (last timed result)").c_str(), la, lb, va, vb, q, r, b);
+ print_row(opt, "auto", la, lb, t);
+ } else if (row == "kernel") {
+ if (!is_divrem) {
+ skip_row(opt, row, "kernel rows time quotient and remainder together, use divrem");
+ } else if (lb == 1) {
+ limbs q(la);
+ limb_t rem = 0;
+ time_row(
+ opt,
+ "kernel",
+ la,
+ lb,
+ [&] {
+ rem = dt::divide_unsigned_short(q, va, vb[0]);
+ escape(&rem);
+ escape(q.data());
+ },
+ [&] {
+ expect_same("kernel divrem quotient", sh, q, ref_q);
+ expect_same("kernel divrem remainder", sh, std::span{&rem, 1}, ref_r);
+ });
+ } else {
+ const std::size_t q_len = la - lb + 1;
+ const std::size_t r_cap = la + 1;
+ std::allocator alloc;
+ dt::scratch_allocator> scratch(r_cap + dt::divide_unsigned_storage_size(la, lb),
+ alloc);
+ const std::span rem = scratch.allocate(r_cap);
+ limbs q(q_len);
+ time_row(
+ opt,
+ "kernel",
+ la,
+ lb,
+ [&] {
+ std::ranges::fill(q, limb_t{0});
+ dt::divide_dispatch(q, rem, va, vb, scratch, alloc);
+ escape(q.data());
+ escape(rem.data());
+ },
+ [&] {
+ expect_same("kernel divrem quotient", sh, q, ref_q);
+ expect_same("kernel divrem remainder", sh, rem, ref_r);
+ });
+ }
+#if defined(BEMAN_BIG_INT_SWEEP_GMP)
+ } else if (row == "gmp") {
+ if (!is_divrem) {
+ skip_row(opt, row, "GMP computes quotient and remainder together, use divrem");
+ continue;
+ }
+ const auto ga = to_gmp(va);
+ const auto gb = to_gmp(vb);
+ std::vector gq(la - lb + 1);
+ std::vector gr(lb);
+ if (lb == 1) {
+ std::vector gq1(la);
+ mp_limb_t rem = 0;
+ time_row(
+ opt,
+ "gmp",
+ la,
+ lb,
+ [&] {
+ rem = __gmpn_divrem_1(gq1.data(), 0, ga.data(), static_cast(la), gb[0]);
+ escape(&rem);
+ escape(gq1.data());
+ },
+ [&] {
+ expect_same("gmp divrem_1 quotient", sh, from_gmp(gq1, gq1.size()), ref_q);
+ expect_same("gmp divrem_1 remainder", sh, limbs{static_cast(rem)}, ref_r);
+ });
+ } else {
+ time_row(
+ opt,
+ "gmp",
+ la,
+ lb,
+ [&] {
+ __gmpn_tdiv_qr(gq.data(),
+ gr.data(),
+ 0,
+ ga.data(),
+ static_cast(la),
+ gb.data(),
+ static_cast(lb));
+ escape(gq.data());
+ escape(gr.data());
+ },
+ [&] {
+ const bb::big_int gq_big(gq.begin(), gq.end());
+ const bb::big_int gr_big(gr.begin(), gr.end());
+ check_division("gmp divrem", la, lb, va, vb, gq_big, gr_big, b);
+ expect_same("gmp divrem quotient", sh, from_gmp(gq, gq.size()), ref_q);
+ expect_same("gmp divrem remainder", sh, from_gmp(gr, gr.size()), ref_r);
+ });
+ }
+ } else if (row == "gmpz") {
+ if (!is_divrem) {
+ skip_row(opt, row, "GMP computes quotient and remainder together, use divrem");
+ continue;
+ }
+ const zint zn(va);
+ const zint zd(vb);
+ zint zq;
+ zint zr;
+ time_row(
+ opt,
+ "gmpz",
+ la,
+ lb,
+ [&] {
+ __gmpz_tdiv_qr(&zq.z, &zr.z, &zn.z, &zd.z);
+ escape(zq.z._mp_d);
+ escape(zr.z._mp_d);
+ },
+ [&] {
+ expect_same("gmpz divrem quotient", sh, zq.get(), ref_q);
+ expect_same("gmpz divrem remainder", sh, zr.get(), ref_r);
+ });
+#endif
+ } else {
+ skip_row(opt, row, "not applicable");
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+// add and sub: AxB limbs with A >= B; a > b always (equal sizes get a top bit set in a and cleared in b).
+// ---------------------------------------------------------------------------
+
+template
+void check_addsub(const char* what, const std::string& sh, const limbs& va, const limbs& vb, const limbs& vc) {
+ for (const u64 p : {prime_a, prime_b}) {
+ const u64 ra = residue(va, p);
+ const u64 rb = residue(vb, p);
+ const u64 want = Add ? (ra + rb) % p : (ra + p - rb) % p;
+ if (residue(vc, p) != want) {
+ fail(std::string(what) + " residue mismatch mod " + std::to_string(p) + " at " + sh);
+ }
+ }
+}
+
+template
+void run_addsub(const options& opt, const std::size_t la, const std::size_t lb) {
+ if (la < lb) {
+ usage_error(opt.op + " needs A >= B limbs, got " + shape_str(la, lb));
+ }
+ std::mt19937_64 rng(splitmix(opt.seed ^ splitmix(la * 1000003 + lb)));
+ auto va = random_limbs(rng, la);
+ auto vb = random_limbs(rng, lb);
+ if (la == lb) {
+ constexpr limb_t top = limb_t{1} << (limb_bits - 1);
+ va.back() |= top;
+ vb.back() &= ~top;
+ if (vb.back() == 0) {
+ vb.back() = 1;
+ }
+ }
+ const auto sh = shape_str(la, lb);
+
+ const bb::big_int a = make_big(va);
+ const bb::big_int b = make_big(vb);
+ limbs ref;
+ {
+ const bb::big_int c = Add ? a + b : a - b;
+ check_addsub("api", sh, va, vb, trimmed(c.representation()));
+ ref = trimmed(c.representation());
+ }
+ const limbs va_ref = trimmed(va);
+
+ for (const auto& row : opt.rows) {
+ if (row == "auto") {
+ bb::big_int c;
+ time_row(
+ opt,
+ "auto",
+ la,
+ lb,
+ [&] {
+ if constexpr (Add) {
+ c = a + b;
+ } else {
+ c = a - b;
+ }
+ escape(&c);
+ escape(c.representation().data());
+ },
+ [&] { expect_same("auto", sh, c.representation(), ref); });
+ } else if (row == "inplace") {
+ // One timed unit is two operations (c += b; c -= b, or the reverse for sub); per-op time is half.
+ {
+ bb::big_int t = a;
+ if constexpr (Add) {
+ t += b;
+ } else {
+ t -= b;
+ }
+ expect_same("inplace first op", sh, t.representation(), ref);
+ }
+ bb::big_int c = a;
+ time_row(
+ opt,
+ "inplace",
+ la,
+ lb,
+ [&] {
+ if constexpr (Add) {
+ c += b;
+ c -= b;
+ } else {
+ c -= b;
+ c += b;
+ }
+ escape(&c);
+ escape(c.representation().data());
+ },
+ [&] { expect_same("inplace pair", sh, c.representation(), va_ref); },
+ 0.5);
+ } else if (row == "kernel") {
+ limbs res(la);
+ bool carry = false;
+ std::size_t size = 0;
+ time_row(
+ opt,
+ "kernel",
+ la,
+ lb,
+ [&] {
+ if constexpr (Add) {
+ carry = dt::add_unsigned_spans(res, va, vb);
+ escape(&carry);
+ } else {
+ size = dt::subtract_unsigned_spans(res, va, vb);
+ escape(&size);
+ }
+ escape(res.data());
+ },
+ [&] {
+ limbs full = res;
+ if (Add && carry) {
+ full.push_back(1);
+ }
+ expect_same("kernel", sh, full, ref);
+ if (!Add && size != trimmed_size(res)) {
+ fail("kernel sub returned a wrong size at " + sh);
+ }
+ });
+#if defined(BEMAN_BIG_INT_SWEEP_GMP)
+ } else if (row == "gmp") {
+ const auto ga = to_gmp(va);
+ const auto gb = to_gmp(vb);
+ std::vector gr(la);
+ mp_limb_t carry = 0;
+ time_row(
+ opt,
+ "gmp",
+ la,
+ lb,
+ [&] {
+ const auto n = static_cast(la);
+ const auto m = static_cast(lb);
+ if (la == lb) {
+ carry = Add ? __gmpn_add_n(gr.data(), ga.data(), gb.data(), n)
+ : __gmpn_sub_n(gr.data(), ga.data(), gb.data(), n);
+ } else {
+ carry = Add ? __gmpn_add(gr.data(), ga.data(), n, gb.data(), m)
+ : __gmpn_sub(gr.data(), ga.data(), n, gb.data(), m);
+ }
+ escape(&carry);
+ escape(gr.data());
+ },
+ [&] {
+ auto full = from_gmp(gr, gr.size());
+ if (Add && carry != 0) {
+ full.push_back(1);
+ }
+ expect_same("gmp", sh, full, ref);
+ });
+ } else if (row == "gmpz") {
+ const zint za(va);
+ const zint zb(vb);
+ zint zc;
+ time_row(
+ opt,
+ "gmpz",
+ la,
+ lb,
+ [&] {
+ if constexpr (Add) {
+ __gmpz_add(&zc.z, &za.z, &zb.z);
+ } else {
+ __gmpz_sub(&zc.z, &za.z, &zb.z);
+ }
+ escape(zc.z._mp_d);
+ },
+ [&] { expect_same("gmpz", sh, zc.get(), ref); });
+#endif
+ } else {
+ skip_row(opt, row, "not applicable");
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+// cmp: N limbs, operands equal except limb 0, so every limb is scanned.
+// ---------------------------------------------------------------------------
+
+void run_cmp(const options& opt, const std::size_t la, const std::size_t lb) {
+ if (la != lb) {
+ usage_error("cmp needs equal limb counts, got " + shape_str(la, lb));
+ }
+ std::mt19937_64 rng(splitmix(opt.seed ^ splitmix(la * 1000003 + lb)));
+ const auto va = random_limbs(rng, la);
+ auto vb = va;
+ vb[0] = va[0] ^ 1;
+ if (vb[0] == 0) {
+ vb[0] = 3; // only reachable for a one-limb operand, whose value must stay nonzero
+ }
+ const int want = va[0] < vb[0] ? -1 : 1;
+ const auto sh = shape_str(la, lb);
+
+ const bb::big_int a = make_big(va);
+ const bb::big_int b = make_big(vb);
+ int s = 0;
+ const auto check = [&](const char* what) {
+ if (s != want) {
+ fail(std::string(what) + " cmp sign mismatch at " + sh);
+ }
+ };
+
+ for (const auto& row : opt.rows) {
+ if (row == "auto") {
+ time_row(
+ opt,
+ "auto",
+ la,
+ lb,
+ [&] {
+ escape(&a);
+ escape(&b);
+ const auto o = a <=> b;
+ s = o < 0 ? -1 : (o > 0 ? 1 : 0);
+ escape(&s);
+ },
+ [&] { check("auto"); });
+ } else if (row == "kernel") {
+ time_row(
+ opt,
+ "kernel",
+ la,
+ lb,
+ [&] {
+ escape(va.data());
+ escape(vb.data());
+ const auto o = dt::compare_unsigned_spans(va, vb);
+ s = o < 0 ? -1 : (o > 0 ? 1 : 0);
+ escape(&s);
+ },
+ [&] { check("kernel"); });
+#if defined(BEMAN_BIG_INT_SWEEP_GMP)
+ } else if (row == "gmp") {
+ const auto ga = to_gmp(va);
+ const auto gb = to_gmp(vb);
+ time_row(
+ opt,
+ "gmp",
+ la,
+ lb,
+ [&] {
+ escape(ga.data());
+ escape(gb.data());
+ const int o = __gmpn_cmp(ga.data(), gb.data(), static_cast(la));
+ s = o < 0 ? -1 : (o > 0 ? 1 : 0);
+ escape(&s);
+ },
+ [&] { check("gmp"); });
+ } else if (row == "gmpz") {
+ const zint za(va);
+ const zint zb(vb);
+ time_row(
+ opt,
+ "gmpz",
+ la,
+ lb,
+ [&] {
+ escape(za.z._mp_d);
+ escape(zb.z._mp_d);
+ const int o = __gmpz_cmp(&za.z, &zb.z);
+ s = o < 0 ? -1 : (o > 0 ? 1 : 0);
+ escape(&s);
+ },
+ [&] { check("gmpz"); });
+#endif
+ } else {
+ skip_row(opt, row, "not applicable");
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+// shl and shr: la = N limbs, lb = S bits (1 <= S < 64 N). GMP's mpn shifts take 1..63 bits, so for S >= 64 the gmp row
+// zero-fills (shl) or skips (shr) the whole limbs and shifts the rest.
+// ---------------------------------------------------------------------------
+
+template
+void check_shift(const char* what, const std::string& sh, const limbs& va, const std::size_t bits, const limbs& vc) {
+ const std::size_t whole = bits / limb_bits;
+ const unsigned partial = static_cast(bits % limb_bits);
+ limbs low(va.begin(), va.begin() + static_cast(whole + (partial != 0 ? 1 : 0)));
+ if (partial != 0) {
+ low.back() &= (limb_t{1} << partial) - 1;
+ }
+ if (low.empty()) {
+ low.push_back(0);
+ }
+ for (const u64 p : {prime_a, prime_b}) {
+ const u64 p2 = pow2mod(bits, p);
+ const u64 ra = residue(va, p);
+ const u64 rc = residue(vc, p);
+ const bool ok = Left ? mulmod(ra, p2, p) == rc : (mulmod(rc, p2, p) + residue(low, p)) % p == ra;
+ if (!ok) {
+ fail(std::string(what) + " residue mismatch mod " + std::to_string(p) + " at " + sh);
+ }
+ }
+}
+
+template
+void run_shift(const options& opt, const std::size_t n, const std::size_t bits) {
+ if (bits < 1 || bits >= n * limb_bits) {
+ usage_error(opt.op + " needs 1 <= S < 64 N, got " + shape_str(n, bits));
}
- bb::big_int q;
- bb::big_int r;
- auto work = [&] {
- if (opt.op == "div") {
- q = a / b;
- escape(&q);
- escape(q.representation().data());
- } else if (opt.op == "rem") {
- r = a % b;
- escape(&r);
- escape(r.representation().data());
+ std::mt19937_64 rng(splitmix(opt.seed ^ splitmix(n * 1000003 + bits)));
+ const auto va = random_limbs(rng, n);
+ const auto sh = shape_str(n, bits);
+
+ const std::size_t whole = bits / limb_bits;
+ const unsigned partial = static_cast(bits % limb_bits);
+ const int s = static_cast(bits);
+
+ const bb::big_int a = make_big(va);
+ limbs ref;
+ {
+ const bb::big_int c = Left ? a << s : a >> s;
+ check_shift("api", sh, va, bits, trimmed(c.representation()));
+ ref = trimmed(c.representation());
+ }
+ const limbs va_ref = trimmed(va);
+
+ for (const auto& row : opt.rows) {
+ if (row == "auto") {
+ bb::big_int c;
+ time_row(
+ opt,
+ "auto",
+ n,
+ bits,
+ [&] {
+ if constexpr (Left) {
+ c = a << s;
+ } else {
+ c = a >> s;
+ }
+ escape(&c);
+ escape(c.representation().data());
+ },
+ [&] { expect_same("auto", sh, c.representation(), ref); });
+ } else if (row == "inplace") {
+ // One timed unit is two operations, c <<= s; c >>= s for shl and c >>= s; c <<= s for shr (the latter
+ // settles on a with its low S bits cleared); per-op time is half.
+ {
+ bb::big_int t = a;
+ if constexpr (Left) {
+ t <<= s;
+ } else {
+ t >>= s;
+ }
+ expect_same("inplace first op", sh, t.representation(), ref);
+ }
+ bb::big_int c = a;
+ time_row(
+ opt,
+ "inplace",
+ n,
+ bits,
+ [&] {
+ if constexpr (Left) {
+ c <<= s;
+ c >>= s;
+ } else {
+ c >>= s;
+ c <<= s;
+ }
+ escape(&c);
+ escape(c.representation().data());
+ },
+ [&] {
+ if constexpr (Left) {
+ expect_same("inplace pair", sh, c.representation(), va_ref);
+ } else {
+ bb::big_int t = c;
+ t >>= s;
+ expect_same("inplace pair", sh, t.representation(), ref);
+ }
+ },
+ 0.5);
+ } else if (row == "kernel") {
+ // The span kernels work in place, so the copy-in of the source is part of the timed loop.
+ if constexpr (Left) {
+ limbs buf(n + whole + 1);
+ std::size_t size = 0;
+ time_row(
+ opt,
+ "kernel",
+ n,
+ bits,
+ [&] {
+ std::ranges::copy(va, buf.begin());
+ size = dt::shift_left_bits(buf, n, bits);
+ escape(&size);
+ escape(buf.data());
+ },
+ [&] { expect_same("kernel", sh, std::span{buf.data(), size}, ref); });
+ } else {
+ // The same two steps as shift_right_bits (move down whole limbs, shift_right_n the rest), without its
+ // debug assertion that every dropped bit is zero.
+ const std::size_t m = n - whole;
+ limbs buf(m);
+ limb_t dropped = 0;
+ time_row(
+ opt,
+ "kernel",
+ n,
+ bits,
+ [&] {
+ std::copy(va.begin() + static_cast(whole), va.end(), buf.begin());
+ if (partial != 0) {
+ dropped = dt::shift_right_n(buf, partial);
+ escape(&dropped);
+ }
+ escape(buf.data());
+ },
+ [&] { expect_same("kernel", sh, buf, ref); });
+ }
+ } else if (row == "kernelip") {
+ if constexpr (!Left) {
+ skip_row(opt, row, "identical to shl kernelip (the pair is shl then shr)");
+ } else {
+ // In-place shl then shr on one buffer, no copy-in; per-op time is half.
+ limbs buf(n + whole + 1);
+ std::size_t size = n;
+ std::ranges::copy(va, buf.begin());
+ time_row(
+ opt,
+ "kernelip",
+ n,
+ bits,
+ [&] {
+ size = dt::shift_left_bits(buf, n, bits);
+ size = dt::shift_right_bits(buf, size, bits);
+ escape(&size);
+ escape(buf.data());
+ },
+ [&] { expect_same("kernelip", sh, std::span{buf.data(), size}, va_ref); },
+ 0.5);
+ }
+#if defined(BEMAN_BIG_INT_SWEEP_GMP)
+ } else if (row == "gmp") {
+ const auto ga = to_gmp(va);
+ std::vector gr(Left ? n + whole + 1 : n - whole);
+ time_row(
+ opt,
+ "gmp",
+ n,
+ bits,
+ [&] {
+ if constexpr (Left) {
+ if (whole != 0) {
+ std::memset(gr.data(), 0, whole * sizeof(mp_limb_t));
+ }
+ if (partial != 0) {
+ gr[n + whole] = __gmpn_lshift(gr.data() + whole, ga.data(), static_cast(n), partial);
+ } else {
+ std::memcpy(gr.data() + whole, ga.data(), n * sizeof(mp_limb_t));
+ gr[n + whole] = 0;
+ }
+ } else {
+ const auto m = static_cast(n - whole);
+ if (partial != 0) {
+ const mp_limb_t dropped = __gmpn_rshift(gr.data(), ga.data() + whole, m, partial);
+ escape(&dropped);
+ } else {
+ std::memcpy(gr.data(), ga.data() + whole, static_cast(m) * sizeof(mp_limb_t));
+ }
+ }
+ escape(gr.data());
+ },
+ [&] { expect_same("gmp", sh, from_gmp(gr, gr.size()), ref); });
+ } else if (row == "gmpz") {
+ const zint za(va);
+ zint zc;
+ time_row(
+ opt,
+ "gmpz",
+ n,
+ bits,
+ [&] {
+ if constexpr (Left) {
+ __gmpz_mul_2exp(&zc.z, &za.z, bits);
+ } else {
+ __gmpz_tdiv_q_2exp(&zc.z, &za.z, bits);
+ }
+ escape(zc.z._mp_d);
+ },
+ [&] { expect_same("gmpz", sh, zc.get(), ref); });
+#endif
} else {
- auto qr = bb::div_rem_to_zero(a, b);
- q = std::move(qr.quotient);
- r = std::move(qr.remainder);
- escape(&q);
- escape(&r);
- escape(q.representation().data());
+ skip_row(opt, row, "not applicable");
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+// tochars and fromchars: N limbs, base in [2, 36]. The kernel rows need a base is_fast_conversion_base accepts (no
+// power of two). GMP's digit-value interfaces are paired with the ASCII-mapped output; get_str destroys its input, so
+// its copy-in is timed (as for gcd). The string is lower case, as std::to_chars writes it.
+// ---------------------------------------------------------------------------
+
+char digit_char(const unsigned d) { return static_cast(d < 10 ? '0' + d : 'a' + (d - 10)); }
+
+std::string map_digits(const unsigned char* d, const std::size_t n) {
+ std::string s(n, '\0');
+ for (std::size_t i = 0; i < n; ++i) {
+ s[i] = digit_char(d[i]);
+ }
+ return s;
+}
+
+void check_base(const options& opt, const std::size_t base) {
+ if (base < 2 || base > 36) {
+ usage_error(opt.op + " needs a base in [2, 36], got " + std::to_string(base));
+ }
+}
+
+// Digits an n-limb value can need in `base`.
+std::size_t digit_capacity(const std::size_t n, const int base) {
+ return n * limb_bits / static_cast(std::bit_width(static_cast(base)) - 1) + 8;
+}
+
+void run_tochars(const options& opt, const std::size_t n, const std::size_t base_arg) {
+ check_base(opt, base_arg);
+ const int base = static_cast(base_arg);
+ std::mt19937_64 rng(splitmix(opt.seed ^ splitmix(n * 1000003 + base_arg)));
+ const auto va = random_limbs(rng, n);
+ const auto sh = shape_str(n, base_arg);
+ const auto cap = digit_capacity(n, base);
+
+ const bb::big_int a = make_big(va);
+ std::string ref(cap, '\0');
+ {
+ const auto r = bb::to_chars(ref.data(), ref.data() + ref.size(), a, base);
+ if (r.ec != std::errc{}) {
+ fail("to_chars failed at " + sh);
+ }
+ ref.resize(static_cast(r.ptr - ref.data()));
+ bb::big_int back;
+ const auto fr = bb::from_chars(ref.data(), ref.data() + ref.size(), back, base);
+ if (fr.ec != std::errc{}) {
+ fail("from_chars failed on the to_chars output at " + sh);
+ }
+ expect_same("tochars round trip", sh, back.representation(), trimmed(va));
+ }
+ const auto same = [&](const char* what, const std::string& got) {
+ if (got != ref) {
+ fail(std::string(what) + " digits differ from the reference at " + sh);
}
};
- const timing t = measure(work, opt);
- if (opt.op == "div") {
- r = a % b;
- } else if (opt.op == "rem") {
- q = a / b;
+
+ for (const auto& row : opt.rows) {
+ if (row == "auto") {
+ std::string buf(cap, '\0');
+ std::to_chars_result r{};
+ time_row(
+ opt,
+ "auto",
+ n,
+ base_arg,
+ [&] {
+ r = bb::to_chars(buf.data(), buf.data() + buf.size(), a, base);
+ escape(&r);
+ escape(buf.data());
+ },
+ [&] { same("auto", std::string(buf.data(), r.ptr)); });
+ } else if (row == "kernel") {
+ if (!dt::is_fast_conversion_base(base)) {
+ skip_row(opt, row, "no span-level kernel for a power-of-two base");
+ continue;
+ }
+ std::allocator alloc;
+ dt::scratch_allocator> scratch(dt::limbs_to_digits_storage_size(n, base), alloc);
+ std::vector out(dt::base_conversion_digit_bound(va, base));
+ std::size_t count = 0;
+ time_row(
+ opt,
+ "kernel",
+ n,
+ base_arg,
+ [&] {
+ count = dt::limbs_to_digits(out, va, base, scratch, alloc);
+ escape(&count);
+ escape(out.data());
+ },
+ [&] {
+ if (scratch.m_offset != 0) {
+ fail("limbs_to_digits left scratch allocated at " + sh);
+ }
+ same("kernel", map_digits(out.data(), count));
+ });
+#if defined(BEMAN_BIG_INT_SWEEP_GMP)
+ } else if (row == "gmp") {
+ const auto ga = to_gmp(va);
+ std::vector tmp(n);
+ std::vector out(cap);
+ std::size_t count = 0;
+ time_row(
+ opt,
+ "gmp",
+ n,
+ base_arg,
+ [&] {
+ std::memcpy(tmp.data(), ga.data(), n * sizeof(mp_limb_t));
+ count = __gmpn_get_str(out.data(), base, tmp.data(), static_cast(n));
+ escape(&count);
+ escape(out.data());
+ },
+ [&] { same("gmp", map_digits(out.data(), count)); });
+ } else if (row == "gmpz") {
+ const zint za(va);
+ std::string buf(cap + 2, '\0');
+ time_row(
+ opt,
+ "gmpz",
+ n,
+ base_arg,
+ [&] {
+ __gmpz_get_str(buf.data(), base, &za.z);
+ escape(buf.data());
+ },
+ [&] { same("gmpz", std::string(buf.c_str())); });
+#endif
+ } else {
+ skip_row(opt, row, "not applicable");
+ }
+ }
+}
+
+void run_fromchars(const options& opt, const std::size_t n, const std::size_t base_arg) {
+ check_base(opt, base_arg);
+ const int base = static_cast(base_arg);
+ std::mt19937_64 rng(splitmix(opt.seed ^ splitmix(n * 1000003 + base_arg)));
+ const auto va = random_limbs(rng, n);
+ const auto sh = shape_str(n, base_arg);
+ const limbs ref = trimmed(va);
+
+ const bb::big_int a = make_big(va);
+ std::string str(digit_capacity(n, base), '\0');
+ {
+ const auto r = bb::to_chars(str.data(), str.data() + str.size(), a, base);
+ if (r.ec != std::errc{}) {
+ fail("to_chars failed at " + sh);
+ }
+ str.resize(static_cast(r.ptr - str.data()));
+ }
+ std::vector digits(str.size());
+ for (std::size_t i = 0; i < str.size(); ++i) {
+ digits[i] = static_cast(dt::digit_value(str[i]));
}
- check_division((opt.op + " (last timed result)").c_str(), la, lb, va, vb, q, r, b);
- print_row(opt, "auto", la, lb, t);
+ for (const auto& row : opt.rows) {
+ if (row == "auto") {
+ bb::big_int c;
+ std::from_chars_result r{};
+ time_row(
+ opt,
+ "auto",
+ n,
+ base_arg,
+ [&] {
+ r = bb::from_chars(str.data(), str.data() + str.size(), c, base);
+ escape(&r);
+ escape(&c);
+ escape(c.representation().data());
+ },
+ [&] {
+ if (r.ec != std::errc{}) {
+ fail("from_chars failed at " + sh);
+ }
+ expect_same("auto", sh, c.representation(), ref);
+ });
+ } else if (row == "kernel") {
+ if (!dt::is_fast_conversion_base(base)) {
+ skip_row(opt, row, "no span-level kernel for a power-of-two base");
+ continue;
+ }
+ std::allocator alloc;
+ dt::scratch_allocator> scratch(
+ dt::digits_to_limbs_storage_size(digits.size(), base), alloc);
+ limbs res(dt::base_conversion_limb_bound(digits.size(), base));
+ std::size_t size = 0;
+ time_row(
+ opt,
+ "kernel",
+ n,
+ base_arg,
+ [&] {
+ size = dt::digits_to_limbs(
+ res, digits, base, static_cast(scratch), alloc);
+ escape(&size);
+ escape(res.data());
+ },
+ [&] {
+ if (scratch.m_offset != 0) {
+ fail("digits_to_limbs left scratch allocated at " + sh);
+ }
+ expect_same("kernel", sh, std::span{res.data(), size}, ref);
+ });
#if defined(BEMAN_BIG_INT_SWEEP_GMP)
- if (opt.gmp) {
- if (opt.op != "divrem") {
- std::printf("# gmp row for %s skipped: GMP computes quotient and remainder together, use divrem\n",
- opt.op.c_str());
- return;
- }
- const std::vector ga(va.begin(), va.end());
- const std::vector gb(vb.begin(), vb.end());
- std::vector gq(la - lb + 1);
- std::vector gr(lb);
- time_gmp(
- opt,
- la,
- lb,
- [&] {
- __gmpn_tdiv_qr(
- gq.data(), gr.data(), 0, ga.data(), static_cast(la), gb.data(), static_cast(lb));
- escape(gq.data());
- escape(gr.data());
- },
- [&] {
- const bb::big_int gq_big(gq.begin(), gq.end());
- const bb::big_int gr_big(gr.begin(), gr.end());
- check_division("gmp divrem", la, lb, va, vb, gq_big, gr_big, b);
- });
+ } else if (row == "gmp") {
+ std::vector gr(dt::base_conversion_limb_bound(digits.size(), base) + 2);
+ long size = 0;
+ time_row(
+ opt,
+ "gmp",
+ n,
+ base_arg,
+ [&] {
+ size = __gmpn_set_str(gr.data(), digits.data(), digits.size(), base);
+ escape(&size);
+ escape(gr.data());
+ },
+ [&] { expect_same("gmp", sh, from_gmp(gr, static_cast(size)), ref); });
+ } else if (row == "gmpz") {
+ zint zc;
+ int rc = 0;
+ time_row(
+ opt,
+ "gmpz",
+ n,
+ base_arg,
+ [&] {
+ rc = __gmpz_set_str(&zc.z, str.c_str(), base);
+ escape(&rc);
+ escape(zc.z._mp_d);
+ },
+ [&] {
+ if (rc != 0) {
+ fail("mpz_set_str failed at " + sh);
+ }
+ expect_same("gmpz", sh, zc.get(), ref);
+ });
+#endif
+ } else {
+ skip_row(opt, row, "not applicable");
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+// gcd: A x B limbs with A >= B; b is odd (GMP's mpn_gcd precondition) for every row. The kernel and gmp rows time the
+// copy-in of their clobbered operands. The kernel row is gcd_unsigned_spans alone: it skips the public driver's
+// Euclidean steps for lopsided operands and its single-limb shortcuts.
+// ---------------------------------------------------------------------------
+
+void run_gcd(const options& opt, const std::size_t la, const std::size_t lb) {
+ if (la < lb) {
+ usage_error("gcd needs A >= B limbs, got " + shape_str(la, lb));
+ }
+ std::mt19937_64 rng(splitmix(opt.seed ^ splitmix(la * 1000003 + lb)));
+ const auto va = random_limbs(rng, la);
+ auto vb = random_limbs(rng, lb);
+ vb[0] |= 1;
+ const auto sh = shape_str(la, lb);
+
+ const bb::big_int a = make_big(va);
+ const bb::big_int b = make_big(vb);
+ limbs ref;
+ {
+ const bb::big_int g = bb::gcd(a, b);
+ if (g <= 0 || !((a % g) == 0) || !((b % g) == 0)) {
+ fail("gcd result does not divide both operands at " + sh);
+ }
+ ref = trimmed(g.representation());
}
+
+ for (const auto& row : opt.rows) {
+ if (row == "auto") {
+ bb::big_int c;
+ time_row(
+ opt,
+ "auto",
+ la,
+ lb,
+ [&] {
+ c = bb::gcd(a, b);
+ escape(&c);
+ escape(c.representation().data());
+ },
+ [&] { expect_same("auto", sh, c.representation(), ref); });
+ } else if (row == "kernel") {
+ const std::size_t cap = la + 1;
+ limbs u(cap);
+ limbs v(cap);
+ limbs t(cap);
+ std::size_t size = 0;
+ time_row(
+ opt,
+ "kernel",
+ la,
+ lb,
+ [&] {
+ std::ranges::copy(va, u.begin());
+ std::ranges::copy(vb, v.begin());
+ size = dt::gcd_unsigned_spans(u, la, v, lb, la >= 3 ? std::span{t} : std::span{});
+ escape(&size);
+ escape(u.data());
+ },
+ [&] { expect_same("kernel", sh, std::span{u.data(), size}, ref); });
+#if defined(BEMAN_BIG_INT_SWEEP_GMP)
+ } else if (row == "gmp") {
+ const auto ga = to_gmp(va);
+ const auto gb = to_gmp(vb);
+ std::vector u(la);
+ std::vector v(lb);
+ std::vector gr(lb);
+ long size = 0;
+ time_row(
+ opt,
+ "gmp",
+ la,
+ lb,
+ [&] {
+ std::memcpy(u.data(), ga.data(), la * sizeof(mp_limb_t));
+ std::memcpy(v.data(), gb.data(), lb * sizeof(mp_limb_t));
+ size = __gmpn_gcd(gr.data(), u.data(), static_cast(la), v.data(), static_cast(lb));
+ escape(&size);
+ escape(gr.data());
+ },
+ [&] { expect_same("gmp", sh, from_gmp(gr, static_cast(size)), ref); });
+ } else if (row == "gmpz") {
+ const zint za(va);
+ const zint zb(vb);
+ zint zc;
+ time_row(
+ opt,
+ "gmpz",
+ la,
+ lb,
+ [&] {
+ __gmpz_gcd(&zc.z, &za.z, &zb.z);
+ escape(zc.z._mp_d);
+ },
+ [&] { expect_same("gmpz", sh, zc.get(), ref); });
#endif
+ } else {
+ skip_row(opt, row, "not applicable");
+ }
+ }
}
// ---------------------------------------------------------------------------
// Command line.
// ---------------------------------------------------------------------------
-std::pair parse_shape(const std::string& tok, const bool square) {
+enum class shape_kind { pair, single_or_equal };
+
+std::pair parse_shape(const std::string& tok, const shape_kind kind) {
const auto x = tok.find_first_of("xX");
std::size_t a = 0;
std::size_t b = 0;
@@ -761,11 +1908,11 @@ std::pair parse_shape(const std::string& tok, const bo
if (a == 0 || b == 0) {
usage_error("shape '" + tok + "' has a zero size");
}
- if (x == std::string::npos && !square) {
+ if (x == std::string::npos && kind == shape_kind::pair) {
usage_error("shape '" + tok + "' needs the AxB form for this op");
}
- if (square && a != b) {
- usage_error("sqr shape '" + tok + "' must be N or NxN");
+ if (kind == shape_kind::single_or_equal && a != b) {
+ usage_error("shape '" + tok + "' must be N or NxN for this op");
}
return {a, b};
}
@@ -788,6 +1935,28 @@ void read_grid(const std::string& file, std::vector& tokens) {
}
}
+std::vector split_commas(const std::string& s) {
+ std::vector out;
+ std::istringstream ss(s);
+ std::string part;
+ while (std::getline(ss, part, ',')) {
+ if (!part.empty()) {
+ out.push_back(part);
+ }
+ }
+ return out;
+}
+
+const char* const all_ops[] = {
+ "mul", "sqr", "div", "rem", "divrem", "add", "sub", "shl", "shr", "cmp", "tochars", "fromchars", "gcd"};
+const char* const all_rows[] = {"auto", "inplace", "kernel", "kernelip", "gmp", "gmpz", "sliced", "unsliced", "fft"};
+
+bool one_of(const std::string& s, const auto& list) { return std::ranges::find(list, s) != std::end(list); }
+
+shape_kind kind_of(const std::string& op) {
+ return (op == "sqr" || op == "cmp") ? shape_kind::single_or_equal : shape_kind::pair;
+}
+
options parse_args(const int argc, char** argv) {
options opt;
std::vector tokens;
@@ -800,7 +1969,9 @@ options parse_args(const int argc, char** argv) {
return argv[++i];
};
if (arg == "--path") {
- opt.path = value();
+ opt.rows = {value()};
+ } else if (arg == "--rows") {
+ opt.rows = split_commas(value());
} else if (arg == "--rounds") {
opt.rounds = static_cast(std::stoul(value()));
} else if (arg == "--round-ms") {
@@ -819,40 +1990,49 @@ options parse_args(const int argc, char** argv) {
tokens.push_back(arg);
}
}
- if (opt.op != "mul" && opt.op != "sqr" && opt.op != "div" && opt.op != "rem" && opt.op != "divrem") {
- usage_error("usage: shape_sweep [--path auto|sliced|unsliced|fft] [--rounds N] "
- "[--round-ms X] [--seed S] [--gmp] [--grid FILE] [AxB ...]");
+ if (!one_of(opt.op, all_ops)) {
+ usage_error("usage: shape_sweep "
+ "[--rows auto,inplace,kernel,kernelip,gmp,gmpz,sliced,unsliced,fft] [--path ROW] [--rounds N] "
+ "[--round-ms X] [--seed S] [--gmp] [--grid FILE] [SHAPE ...]");
}
if (opt.rounds == 0 || opt.round_ms <= 0.0) {
usage_error("--rounds and --round-ms must be positive");
}
- if (opt.path != "auto" && opt.op != "mul") {
- usage_error("--path other than auto is for mul only");
+ if (opt.gmp && !has_row(opt, "gmp")) {
+ opt.rows.emplace_back("gmp");
}
- if (opt.path != "auto" && opt.path != "sliced" && opt.path != "unsliced" && opt.path != "fft") {
- usage_error("unknown --path " + opt.path);
+ if (opt.rows.empty()) {
+ usage_error("--rows needs at least one row");
}
+ for (const auto& row : opt.rows) {
+ if (!one_of(row, all_rows)) {
+ usage_error("unknown row '" + row + "'");
+ }
+ if ((row == "sliced" || row == "unsliced" || row == "fft") && opt.op != "mul") {
+ usage_error("--path " + row + " is for mul only");
+ }
+ if ((row == "gmp" || row == "gmpz") && limb_bits != 64) {
+ usage_error("the " + row + " row needs 64-bit limbs");
+ }
+ if (row == "fft" && limb_bits != 64) {
+ usage_error("--path fft needs 64-bit limbs");
+ }
#if !defined(BEMAN_BIG_INT_SWEEP_GMP)
- if (opt.gmp) {
- usage_error("--gmp needs a build with -DBEMAN_BIG_INT_SWEEP_GMP");
- }
+ if (row == "gmp" || row == "gmpz") {
+ usage_error("the " + row + " row (--gmp) needs a build with -DBEMAN_BIG_INT_SWEEP_GMP");
+ }
#endif
- if (opt.gmp && limb_bits != 64) {
- usage_error("--gmp needs 64-bit limbs");
- }
- if (opt.path == "fft" && limb_bits != 64) {
- usage_error("--path fft needs 64-bit limbs");
- }
#if !defined(BEMAN_BIG_INT_HAS_SHAPE_DISPATCH)
- if (opt.path == "sliced" || opt.path == "unsliced") {
- usage_error("--path " + opt.path + " needs a tree that defines BEMAN_BIG_INT_HAS_SHAPE_DISPATCH");
- }
+ if (row == "sliced" || row == "unsliced") {
+ usage_error("--path " + row + " needs a tree that defines BEMAN_BIG_INT_HAS_SHAPE_DISPATCH");
+ }
#endif
+ }
if (tokens.empty()) {
usage_error("no shapes given");
}
for (const auto& tok : tokens) {
- opt.shapes.push_back(parse_shape(tok, opt.op == "sqr"));
+ opt.shapes.push_back(parse_shape(tok, kind_of(opt.op)));
}
return opt;
}
@@ -862,24 +2042,44 @@ options parse_args(const int argc, char** argv) {
int main(int argc, char** argv) {
const options opt = parse_args(argc, argv);
+ std::string rows;
+ for (const auto& r : opt.rows) {
+ rows += (rows.empty() ? "" : ",") + r;
+ }
std::printf("%s\n", const_line().c_str());
- std::printf("# op=%s path=%s rounds=%u round_ms=%g seed=%llu gmp=%d\n",
+ std::printf("# op=%s rows=%s rounds=%u round_ms=%g seed=%llu\n",
opt.op.c_str(),
- opt.path.c_str(),
+ rows.c_str(),
opt.rounds,
opt.round_ms,
- static_cast(opt.seed),
- opt.gmp ? 1 : 0);
+ static_cast(opt.seed));
#if defined(BEMAN_BIG_INT_SWEEP_GMP)
std::printf("# gmp_version=%s\n", __gmp_version);
#endif
std::printf("op,path,la,lb,reps,rounds,median_ns,min_ns,round_ms_actual\n");
for (const auto& [la, lb] : opt.shapes) {
- if (opt.op == "mul") {
+ const auto& op = opt.op;
+ if (op == "mul") {
run_mul(opt, la, lb);
- } else if (opt.op == "sqr") {
+ } else if (op == "sqr") {
run_sqr(opt, la);
+ } else if (op == "add") {
+ run_addsub(opt, la, lb);
+ } else if (op == "sub") {
+ run_addsub(opt, la, lb);
+ } else if (op == "cmp") {
+ run_cmp(opt, la, lb);
+ } else if (op == "shl") {
+ run_shift(opt, la, lb);
+ } else if (op == "shr") {
+ run_shift(opt, la, lb);
+ } else if (op == "tochars") {
+ run_tochars(opt, la, lb);
+ } else if (op == "fromchars") {
+ run_fromchars(opt, la, lb);
+ } else if (op == "gcd") {
+ run_gcd(opt, la, lb);
} else {
run_div(opt, la, lb);
}
diff --git a/tests/beman/big_int/perf/tools/README.md b/tests/beman/big_int/perf/tools/README.md
new file mode 100644
index 00000000..ceab5ce8
--- /dev/null
+++ b/tests/beman/big_int/perf/tools/README.md
@@ -0,0 +1,27 @@
+
+
+# GMP gap analysis tools
+
+Scripts that produced the data in [`../gmp_gap_analysis.md`](../gmp_gap_analysis.md). The harness itself is
+`../shape_sweep.cpp` (CMake target `beman.big_int.benchmarks.shape_sweep`, built with
+`-DBEMAN_BIG_INT_BUILD_BENCHMARKS=ON -DBEMAN_BIG_INT_SHAPE_SWEEP_ONLY=ON -DBEMAN_BIG_INT_SWEEP_GMP=ON`). Nothing here is part
+of the CMake build or the test suite.
+
+| Tool | What it does and how it is invoked |
+|---|---|
+| `gap_sweep.sh` | `gap_sweep.sh [--seed S] [--ops a,b] [--bands small,medium,large] [--pin "taskset -c 2"]`: runs the report's size grid with every applicable row; writes one CSV (`#const` line once, then `op,path,la,lb,reps,rounds,median_ns,min_ns,round_ms_actual`). About 5 minutes per full run. |
+| `selftest.sh` | `selftest.sh `: every op and row at small shapes; exit status 0 means all correctness checks passed (timings meaningless). |
+| `summarize.py` | `summarize.py a.csv [b.csv] [probe.csv] [--ghz G] > summary.md`: per op and band ratio tables (auto/gmpz, kernel/gmp, inplace/gmpz, auto-kernel ns), top gaps, probe table; with two sweep CSVs also the seed spread. Run with `python3 -I`. |
+| `compare.py` | `compare.py [heat\|tables]`: the report's cross-configuration tables (comma-separated CSV lists average seeds). |
+| `probe_tables.py` | `probe_tables.py [n] [opt]`: the report's probe table (cycles/limb per compiler, M4 ns/limb x 4.47). |
+| `bucket_tables.py` | `bucket_tables.py `: compact profile table plus the "time saved if linear helpers ran at GMP speed" estimate. |
+| `prof.sh` | `prof.sh [filter]` (Linux x86-64, `perf`, `kernel.perf_event_paranoid<=1`): LBR profiles and flame graphs for the report's 12 shapes. Env: `FLAMEGRAPH_DIR` (default `$HOME/FlameGraph`), `PIN`, `MINW`, `LABEL`, `SEED`. |
+| `classify.py` | `classify.py > buckets.md`: buckets the folded stacks from `prof.sh` by the frame nearest the leaf and scales by the median. |
+| `probe/` | Standalone linear-kernel probe (`probe.cpp`: library kernels vs loop variants vs GMP mpn). `build_mac.sh` / `run_mac.sh` (env `GMP`, `BIN_DIR`, `OUT`) write an `opt,kernel,variant,n,ns_per_limb` CSV; `build_x64.sh` / `run_x64.sh` (env `GMP`, `BIN_DIR`, `COMPILERS`, `OUT`, `CPU`, `LIMBS`) write cycles/limb CSVs and `add_n` loop listings. `probe --check` verifies every kernel against GMP. |
+
+The run order used for the report: build the harness per configuration, `gap_sweep.sh` per binary, `summarize.py` and
+`compare.py` for the tables, `prof.sh` then `classify.py` then `bucket_tables.py` for the profiles, the probe scripts for
+the probe tables.
diff --git a/tests/beman/big_int/perf/tools/bucket_tables.py b/tests/beman/big_int/perf/tools/bucket_tables.py
new file mode 100755
index 00000000..f70a6dff
--- /dev/null
+++ b/tests/beman/big_int/perf/tools/bucket_tables.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+# SPDX-License-Identifier: BSL-1.0
+# bucket_tables.py : compact profile table + "ns saved if linear helpers ran at GMP c/l" estimate
+import re, sys
+secs = re.split(r"\n## ", open(sys.argv[1]).read())[1:]
+rows = []
+for s in secs:
+ h = s.split("\n")[0]
+ name = h.split(" (")[0]
+ m = re.search(r"auto median ([\d.]+) ns.*?gmpz median ([\d.]+) ns", h)
+ inc = re.findall(r"ours ([\d.]+)%", s)
+ ours, gmp = [], []
+ for l in s.split("\n"):
+ c = [x.strip() for x in l.strip("|").split("|")]
+ if len(c) >= 6 and re.match(r"^[\d.]+$", c[1]) and c[0] != "bucket":
+ ours.append((c[0], float(c[1])))
+ if re.match(r"^[\d.]+$", c[4]): gmp.append((c[3], float(c[4])))
+ if not m or "inplace" in name: continue
+ rows.append((name, float(m.group(1)), float(m.group(2)), ours, gmp, float(inc[-1]) if inc else 0))
+def fmtb(b): return ", ".join(f"{n} {p:.0f}%" for n, p in b[:4] if p >= 1)
+def tm(ns): return f"{ns/1e6:.2f} ms" if ns >= 1e6 else f"{ns/1e3:.1f} us" if ns >= 1e3 else f"{ns:.0f} ns"
+print("| profile (x64 gcc14) | auto | gmpz | ours top buckets | GMP top buckets | span_ops incl. |")
+print("|---|---:|---:|---|---|---:|")
+for n, a, g, o, gm, inc in rows:
+ print(f"| {n} | {tm(a)} | {tm(g)} | {fmtb(o)} | {fmtb(gm)} | {inc:.0f}% |")
+print()
+print("| profile | span_ops incl. ns | saved at r=1.5 | saved at r=2.0 | auto -> projected | gmpz | projected ratio |")
+print("|---|---:|---:|---:|---:|---:|---:|")
+for n, a, g, o, gm, inc in rows:
+ if n.startswith("add 16") or n.startswith("mul 64x64") or n.startswith("add 1000"): continue
+ if n.startswith("gcd"):
+ inc = sum(p for b, p in o if b in ("span_ops", "wide_ops"))
+ ns = a * inc / 100
+ s1, s2 = ns * (1 - 1 / 1.5), ns * (1 - 1 / 2.0)
+ print(f"| {n} | {tm(ns)} | {tm(s1)} | {tm(s2)} | {tm(a-s1)} to {tm(a-s2)} | {tm(g)} | {(a-s1)/g:.2f} to {(a-s2)/g:.2f} (now {a/g:.2f}) |")
diff --git a/tests/beman/big_int/perf/tools/classify.py b/tests/beman/big_int/perf/tools/classify.py
new file mode 100755
index 00000000..fcb853d3
--- /dev/null
+++ b/tests/beman/big_int/perf/tools/classify.py
@@ -0,0 +1,201 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+# SPDX-License-Identifier: BSL-1.0
+# classify.py : bucket .folded stacks by the frame nearest the leaf, scale by median_ns.
+NAMES = {'wide': ['borrowing_sub', 'carrying_add', 'divide_wide_by_wide', 'divide_wide_by_wide_portable', 'from_int', 'funnel_shr', 'high_mul', 'high_mul_portable', 'narrowing_div', 'narrowing_div_portable', 'overflowing_add', 'overflowing_mul', 'overflowing_sub', 'reciprocal_word', 'reciprocal_word_3by2', 'select', 'to_int', 'volatile', 'widening_mul'], 'div': ['barrett_blocks', 'barrett_preinv_storage_size', 'barrett_storage_size', 'burnikel_ziegler_storage_size', 'divappr_quotient_slack', 'divide_barrett', 'divide_barrett_preinv', 'divide_burnikel_ziegler', 'divide_dc_2n1n', 'divide_dc_3n2n', 'divide_dc_basecase', 'divide_dc_divappr', 'divide_dispatch', 'divide_dispatch_q', 'divide_quotient', 'divide_quotient_appr', 'divide_quotient_storage_size', 'divide_unsigned', 'divide_unsigned_approx', 'divide_unsigned_storage_size', 'reciprocal_span', 'reciprocal_span_storage_size'], 'conv': ['append_squared_power', 'approximate_ceil_div_log2', 'approximate_ceil_mul_log2', 'base_conversion_chunk_count', 'base_conversion_digit_bound', 'base_conversion_limb_bound', 'basecase_digits_to_limbs', 'basecase_limbs_to_digits', 'build_power_table_for_chunks', 'build_power_table_for_value', 'combine_level', 'digit_value_buffer', 'digits_to_limbs', 'digits_to_limbs_storage_size', 'divide_short_preinv_in_place', 'emit_digits', 'fast_digits_to_limbs_profitable', 'fast_input_group_size', 'fast_limbs_to_digits_profitable', 'fast_output_group_size', 'is_fast_conversion_base', 'limb_max_input_digits', 'limb_max_input_digits_naive', 'limb_max_power', 'limb_pow_naive', 'limbs_to_digits', 'limbs_to_digits_storage_size', 'make_short_divisor', 'power_table_storage_size', 'span', 'square_into', 'value_bit_width', 'value_reaches_power', 'write_chunk_digits'], 'gcd': ['first', 'gcd_lehmer_step', 'gcd_limbs', 'gcd_short', 'gcd_unsigned_spans', 'lehmer_simulate', 'value'], 'toom': ['add_mod_bnm1', 'fft_choose_coeff_bits', 'fft_coeff_count', 'fft_cyclic_fp_storage_size', 'fft_cyclic_int_storage_size', 'fft_cyclic_next_size_properties', 'fft_cyclic_storage_size', 'fft_model_worthwhile', 'fft_mul_fp_storage_size', 'fft_mul_int_storage_size', 'fft_mul_storage_size', 'fft_mul_worthwhile', 'fft_transform_length', 'fold_mod_bnm1', 'fold_mod_bnp1', 'hooks', 'icbrt_floor', 'isqrt_floor', 'karatsuba_storage_size', 'mul_should_slice', 'mul_slice_zone_ratio', 'mul_slice_zones_valid', 'multiply_dispatch', 'multiply_fft', 'multiply_fft_cyclic', 'multiply_fft_cyclic_next_size', 'multiply_karatsuba', 'multiply_long', 'multiply_mod_bnm1', 'multiply_mod_bnm1_next_size', 'multiply_mod_bnm1_storage_size', 'multiply_power_of_two', 'multiply_runtime', 'multiply_runtime_any', 'multiply_runtime_sliced', 'multiply_runtime_unsliced', 'multiply_toom_cook_3', 'multiply_toom_cook_4', 'multiply_toom_cook_6_5', 'multiply_toom_cook_8_5', 'signed_axpy', 'solve_subsystem', 'solve_subsystem_85', 'square_dispatch', 'square_fft', 'square_fft_fp_storage_size', 'square_fft_int_storage_size', 'square_fft_storage_size', 'square_fft_worthwhile', 'square_karatsuba', 'square_long', 'square_runtime', 'square_toom_cook_3', 'square_toom_cook_4', 'square_toom_cook_6_5', 'square_toom_cook_8_5', 'stage_pow2_scaled', 'toom_cook_3_refuses_shape', 'toom_cook_3_storage_size', 'toom_cook_4_storage_size', 'toom_cook_6_5_storage_size', 'toom_cook_8_5_storage_size', 'toom_ladder_storage_size'], 'fft': ['add', 'cpu_has_avx2_fma', 'crt3_make_constants', 'crt_inverse', 'fft_cyclic_fp_storage_size', 'fft_cyclic_int_storage_size', 'fft_cyclic_storage_size', 'fft_min_adicity', 'fft_pack', 'fft_pack_fp', 'fft_recompose', 'fft_recompose_cyclic', 'fp_center', 'fp_mulmod', 'fp_reduce_to_0n', 'fp_reduce_to_pm1n', 'fp_ws', 'from_mont', 'heap', 'int_ws', 'inv', 'make_n_prime', 'make_r_squared', 'mont_mul', 'mul', 'multiply_fft', 'multiply_fft_cyclic', 'multiply_mod_bnm1', 'multiply_mod_bnp1', 'ntt_build_twiddles', 'ntt_forward', 'ntt_fp_build_twiddles', 'ntt_fp_dispatch', 'ntt_fp_forward_avx2', 'ntt_fp_forward_impl', 'ntt_fp_forward_neon', 'ntt_fp_forward_scalar', 'ntt_fp_inverse_avx2', 'ntt_fp_inverse_impl', 'ntt_fp_inverse_neon', 'ntt_fp_inverse_scalar', 'ntt_fp_pointwise_avx2', 'ntt_fp_pointwise_impl', 'ntt_fp_pointwise_neon', 'ntt_fp_pointwise_scalar', 'ntt_inverse', 'ntt_pointwise', 'pow', 'reduce', 'root', 'select_kernels', 'square_fft', 'sub', 'to_mont', 'vmulmod', 'vreduce_to_pm1n', 'ws'], 'span': ['add_into_tmp', 'add_shifted', 'add_unsigned_spans', 'add_unsigned_spans_and_shift_right_n', 'add_unsigned_spans_no_carry', 'decrement_span', 'div_2by1_preinv', 'funnel_shl', 'increment_span', 'is_power_of_two_span', 'is_span_zero', 'limb_or_zero', 'mod_unsigned_short', 'mul_add_single_limb_in_place', 'multiply_single_limb', 'recompose', 'recover_pair', 'shift_left_n', 'shift_left_one', 'shift_right_n', 'shift_right_one', 'submul_single_limb', 'submul_single_limb_wide', 'subtract_shifted_unsigned', 'subtract_unsigned_spans', 'subtract_unsigned_spans_and_shift_right_n', 'subtract_unsigned_spans_no_borrow', 'subtract_unsigned_spans_signed', 'trailing_zero_bits_span', 'trimmed_size_span']}
+
+import os
+import re
+import sys
+
+STRIP_TARGS = re.compile(r"<[^<>]*>")
+
+
+def short(frame):
+ f = frame
+ for _ in range(8):
+ g = STRIP_TARGS.sub("", f)
+ if g == f:
+ break
+ f = g
+ f = re.sub(r"\(.*$", "", f)
+ f = re.sub(r"\s*\[.*\]$", "", f)
+ f = f.strip()
+ return f.split("::")[-1] if "::" in f else f
+
+
+ALLOC = re.compile(
+ r"^(malloc|free|cfree|calloc|realloc|_?_?libc_(malloc|free|calloc)|_int_malloc|_int_free|malloc_consolidate|"
+ r"unlink_chunk|operator new|operator delete|new|delete|_Znwm|_ZdlPv.*|_ZnwmSt.*|aligned_alloc|posix_memalign|"
+ r"__memset.*|__memcpy.*|__memmove.*|memset|memcpy|memmove|__libc_.*|_dl_.*|__gmp_default_(allocate|free|reallocate)|"
+ r"__gmp_tmp_.*|.*tmp_alloc.*|.*scratch.*heap.*|_?_?alloc.*|.*_free|.*allocate.*|.*deallocate.*|__clone.*|brk|sbrk|"
+ r"page_fault|asm_exc_page_fault|_?_?mmap|munmap|madvise)$"
+)
+SPANRE = re.compile(r"(unsigned_spans|_shifted|into_tmp|shift_(left|right)_n|single_limb|recompose|div_[23]by[12]|"
+ r"mod_unsigned|_span$|_span_|divide_unsigned_short|borrow_out)")
+MEMBER = {"construct_at", "add_into", "sub_into", "add_in_place", "sub_in_place", "limb_ptr", "grow", "basic_big_int",
+ "operator+", "operator-", "representation", "compare_limb_magnitudes", "alloc_limbs", "alloc_limbs_from"}
+SKIP = re.compile(r"^(bit_cast|_mm\d*_.*|__builtin.*|reduce|select|volatile|to_int|from_int)$")
+ASM = re.compile(r".*(long_runtime|multiply_long|square_long|ifma|bmi2_adx|_mulx|adx_).*", re.I)
+OURS = [
+ ("alloc_mem", lambda n: bool(ALLOC.match(n))),
+ ("asm_kernel", lambda n: bool(ASM.match(n))),
+ ("span_ops", lambda n: n in NAMES["span"] or bool(SPANRE.search(n))),
+ ("wide_ops", lambda n: n in NAMES["wide"]),
+ ("fft_ntt", lambda n: n in NAMES["fft"] or bool(re.search(r"fft|ntt|bnm1|fp_", n))),
+ ("toom_karatsuba", lambda n: n in NAMES["toom"] or bool(re.search(r"toom|karatsuba|basecase_mul|multiply", n))),
+ ("division", lambda n: n in NAMES["div"] or bool(re.search(r"divid|barrett|reciprocal|burnikel", n))),
+ ("base_conv", lambda n: n in NAMES["conv"] or bool(re.search(r"to_chars|from_chars|base_conv|radix|digit|chunk", n))),
+ ("gcd", lambda n: n in NAMES["gcd"] or bool(re.search(r"gcd|lehmer", n))),
+ ("member_inline", lambda n: n in MEMBER),
+]
+G = lambda p: re.compile(p)
+GMP = [
+ ("alloc_mem", G(r"^(malloc|free|cfree|calloc|realloc|_?_?libc_.*|_int_malloc|_int_free|malloc_consolidate|unlink_chunk|"
+ r"__memset.*|__memcpy.*|__memmove.*|memset|memcpy|memmove|__gmp_default_.*|__gmp_tmp_.*|"
+ r"__gmpz_(init|clear|realloc|realloc2|init2)|_?_?gmpz_realloc.*|_?_?gmp_.*alloc.*|brk|page_fault|"
+ r"asm_exc_page_fault)$")),
+ ("linear", G(r"bdiv_q_1|divexact_by3c|pi1_bdiv_q_1")),
+ ("fft", G(r"fft|^mpn_mul_fft|mulmod_bnm1|mullo|sqrmod")),
+ ("toom", G(r"toom|interpolate|mpn_toom|mul_n$|sqr_n$|^_?_?gmpn_(mul|sqr)$|_?_?gmpn_nussbaumer|mul_basecase_n")),
+ ("basecase", G(r"mul_basecase|sqr_basecase|addmul|submul|mul_1|mul_2|mul_3|mul_4|sqr_diag|mul_n_basecase|^_?_?gmpn_mul$")),
+ ("gcd", G(r"hgcd|gcd|matrix22|gcdext|jacobi|binvert|mpn_sub_ndiv|^div2$")),
+ ("division", G(r"sbpi1|dcpi1|mu_div|mu_bdiv|divrem|invert|tdiv|div_qr|bdiv|pi1|div_q|divexact|preinv|redc|^_?_?gmpn_.*_div")),
+ ("conv", G(r"get_str|set_str|mpn_.*bc|mp_bases|get_digit|powm|dc_get|dc_set|mpz_.*str|mpn_compute_powtab|powtab")),
+ ("linear", G(r"(^|_)(add_n|sub_n|add|sub|lshift|rshift|addlsh|sublsh|rsh1|lsh1|add_n_sub_n|copyi|copyd|com|zero|"
+ r"divexact_by3c|bdiv_q_1|cnd_|cmp|zero_p|neg|abs_sub|add_1|sub_1|incr_u|decr_u|rsblsh|rsh1add|rsh1sub|"
+ r"sumdiff|lsh2|rsh2|addcnd|divrem_1|mod_1|mod_34lsub1|divexact_1|norm|sec_)\b|gmpz_(add|sub|cmp|mul_2exp|tdiv_q_2exp)")),
+]
+
+
+def bucket_of(frames, kind):
+ lst = OURS if kind == "ours" else GMP
+ shorts = [short(f) for f in frames]
+ for fr in reversed(frames):
+ n = short(fr)
+ if kind == "ours":
+ if SKIP.match(n):
+ continue
+ if n == "operator" and any(x in ("add_into", "sub_into", "operator+", "operator-") for x in shorts):
+ return "member_inline"
+ if kind == "gmp":
+ n = re.sub(r"^_?_?gmpn_|^_?_?gmpz_", lambda m: m.group(0), n)
+ nn = re.sub(r"^__gmp[nz]_", "", n)
+ else:
+ nn = n
+ for b, t in lst:
+ if kind == "ours":
+ if t(n):
+ return b
+ elif t.search(n) or t.search(nn):
+ return b
+ return "other"
+
+
+def fold_load(path):
+ rows = []
+ for line in open(path):
+ line = line.rstrip("\n")
+ if not line:
+ continue
+ stack, _, cnt = line.rpartition(" ")
+ rows.append((stack.split(";"), int(cnt)))
+ return rows
+
+
+def hist(path, kind):
+ h = {}
+ tot = 0
+ nomain = 0
+ if kind == "ours":
+ h["(span_ops inclusive)"] = 0
+ for frames, c in fold_load(path):
+ b = bucket_of(frames, kind)
+ if kind == "ours" and any(short(f) in NAMES["span"] or SPANRE.search(short(f)) for f in frames):
+ h["(span_ops inclusive)"] += c
+ h[b] = h.get(b, 0) + c
+ tot += c
+ if not any(short(f) == "main" for f in frames):
+ nomain += c
+ return h, tot, nomain
+
+
+def csv_median(path, op, row, la, lb):
+ try:
+ for l in open(path):
+ f = l.strip().split(",")
+ if len(f) >= 9 and f[0] == op and f[1] == row and f[2] == la and f[3] == lb:
+ return float(f[6])
+ except OSError:
+ pass
+ return None
+
+
+def out_median(path):
+ try:
+ for l in open(path):
+ f = l.strip().split(",")
+ if len(f) >= 9 and f[0] != "op":
+ return float(f[6])
+ except OSError:
+ pass
+ return None
+
+
+def main():
+ pdir, csv = sys.argv[1], sys.argv[2]
+ out = []
+ for name in sorted(os.listdir(pdir)):
+ if not name.endswith("_auto.folded"):
+ continue
+ base = name[: -len("_auto.folded")]
+ op, shape = base.split("_", 1)
+ la, _, lb = shape.partition("x")
+ lb = lb or la
+ o_med = csv_median(csv, op, "auto", la, lb)
+ g_med = csv_median(csv, op, "gmpz", la, lb)
+ o_src = g_src = "gap csv"
+ if o_med is None:
+ o_med, o_src = out_median(os.path.join(pdir, base + "_auto.out")), "profile run"
+ if g_med is None:
+ g_med, g_src = out_median(os.path.join(pdir, base + "_gmpz.out")), "profile run"
+ gpath = os.path.join(pdir, base + "_gmpz.folded")
+ oh, o_tot, onm = hist(os.path.join(pdir, name), "ours")
+ gh, gt, gnm = hist(gpath, "gmp") if os.path.exists(gpath) else ({}, 0, 0)
+ out.append("## %s %s (auto median %s ns [%s], gmpz median %s ns [%s]; no-main stacks ours %.1f%%, GMP %.1f%%)\n" % (
+ op, shape, o_med, o_src, g_med, g_src, 100.0 * onm / max(o_tot, 1), 100.0 * gnm / max(gt, 1)))
+ out.append("| bucket | ours % | ours ns | GMP bucket | GMP % | GMP ns |\n|---|---|---|---|---|---|")
+ incl = oh.pop("(span_ops inclusive)", 0)
+ gh.pop("(span_ops inclusive)", None)
+ ob = sorted(oh.items(), key=lambda kv: -kv[1])
+ gb = sorted(gh.items(), key=lambda kv: -kv[1])
+ for i in range(max(len(ob), len(gb))):
+ o = ob[i] if i < len(ob) else None
+ g = gb[i] if i < len(gb) else None
+ os_ = ("%s | %.1f | %s" % (o[0], 100.0 * o[1] / o_tot, "%.0f" % (o_med * o[1] / o_tot) if o_med else "-")) if o else " | | "
+ gs = ("%s | %.1f | %s" % (g[0], 100.0 * g[1] / gt, "%.0f" % (g_med * g[1] / gt) if g_med else "-")) if g else " | | "
+ out.append("| %s | %s |" % (os_, gs))
+ out.append("")
+ out.append("span_ops inclusive (any span_ops frame on the stack, incl. wide_ops leaves beneath it): ours %.1f%%\n" % (
+ 100.0 * incl / max(o_tot, 1)))
+ for name in sorted(os.listdir(pdir)):
+ if not name.endswith("_inplace.folded"):
+ continue
+ base = name[: -len("_inplace.folded")]
+ op, shape = base.split("_", 1)
+ la, _, lb = shape.partition("x")
+ med = csv_median(csv, op, "inplace", la, lb or la)
+ if med is None:
+ med = out_median(os.path.join(pdir, base + "_inplace.out"))
+ h, t, nm = hist(os.path.join(pdir, name), "ours")
+ h.pop("(span_ops inclusive)", None)
+ out.append("## %s %s inplace (ours only, median %s ns; no-main %.1f%%)\n" % (op, shape, med, 100.0 * nm / max(t, 1)))
+ out.append("| bucket | ours % | ours ns |\n|---|---|---|")
+ for b, c in sorted(h.items(), key=lambda kv: -kv[1]):
+ out.append("| %s | %.1f | %s |" % (b, 100.0 * c / t, "%.0f" % (med * c / t) if med else "-"))
+ out.append("")
+ sys.stdout.write("\n".join(out) + "\n")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/beman/big_int/perf/tools/compare.py b/tests/beman/big_int/perf/tools/compare.py
new file mode 100755
index 00000000..77df534b
--- /dev/null
+++ b/tests/beman/big_int/perf/tools/compare.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+# SPDX-License-Identifier: BSL-1.0
+# compare.py -> markdown fragments for the report
+import sys, collections, statistics, math
+
+def load(files):
+ d = collections.defaultdict(list); order = []
+ for fn in files.split(","):
+ for l in open(fn):
+ if l[0] == "#" or l.startswith("op,") or not l.strip():
+ continue
+ p = l.strip().split(",")
+ k = (p[0], int(p[2]), int(p[3]))
+ if k not in order: order.append(k)
+ d[k + (p[1],)].append(float(p[6]))
+ return {k: statistics.mean(v) for k, v in d.items()}, order
+
+X, order = load(sys.argv[1]); C, _ = load(sys.argv[2]); G13, _ = load(sys.argv[3]); NI, _ = load(sys.argv[4]); M, morder = load(sys.argv[5])
+
+def r(D, op, la, lb, a, b):
+ x, y = D.get((op, la, lb, a)), D.get((op, la, lb, b))
+ return None if x is None or y is None else x / y
+def f(x, d=2): return "-" if x is None else f"{x:.{d}f}"
+def band(op, la):
+ lim = (16, 4000) if op == "divrem" else (16, 2000)
+ return "small" if la <= lim[0] else "medium" if la <= lim[1] else "large"
+
+ops = []
+for k in order:
+ if k[0] not in ops: ops.append(k[0])
+mode = sys.argv[6] if len(sys.argv) > 6 else "tables"
+if mode == "heat":
+ print("| op | band | x64 gcc14 auto/gmpz (geomean, worst) | M4 auto/gmpz (geomean, worst) |")
+ print("|---|---|---|---|")
+ for op in ops:
+ for b in ("small", "medium", "large"):
+ sh = [k for k in order if k[0] == op and band(op, k[1]) == b]
+ if not sh: continue
+ def agg(D):
+ v = [r(D, *k, "auto", "gmpz") for k in sh]; v = [x for x in v if x]
+ return "-" if not v else f"{math.exp(sum(map(math.log, v))/len(v)):.2f} ({max(v):.2f})"
+ print(f"| {op} | {b} | {agg(X)} | {agg(M)} |")
+ sys.exit()
+for op in ops:
+ print(f"\n#### {op}\n")
+ print("| shape | x64 auto/gmpz | x64 kern/gmp | x64 inpl/gmpz | x64 auto-kern ns | clang auto/gmpz | clang kern/gmp | gcc13 kern/gmp | noIFMA auto/gmpz | M4 auto/gmpz | M4 kern/gmp | M4 inpl/gmpz | M4 auto-kern ns |")
+ print("|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|")
+ for k in order:
+ if k[0] != op: continue
+ a, kn = X.get(k + ("auto",)), X.get(k + ("kernel",))
+ d = None if a is None or kn is None else a - kn
+ ma, mk = M.get(k + ("auto",)), M.get(k + ("kernel",))
+ md = None if ma is None or mk is None else ma - mk
+ print(f"| {k[1]}x{k[2]} | {f(r(X,*k,'auto','gmpz'))} | {f(r(X,*k,'kernel','gmp'))} | {f(r(X,*k,'inplace','gmpz'))} | {f(d,0)} | "
+ f"{f(r(C,*k,'auto','gmpz'))} | {f(r(C,*k,'kernel','gmp'))} | {f(r(G13,*k,'kernel','gmp'))} | {f(r(NI,*k,'auto','gmpz'))} | "
+ f"{f(r(M,*k,'auto','gmpz'))} | {f(r(M,*k,'kernel','gmp'))} | {f(r(M,*k,'inplace','gmpz'))} | {f(md,0)} |")
diff --git a/tests/beman/big_int/perf/tools/gap_sweep.sh b/tests/beman/big_int/perf/tools/gap_sweep.sh
new file mode 100755
index 00000000..6a18ccff
--- /dev/null
+++ b/tests/beman/big_int/perf/tools/gap_sweep.sh
@@ -0,0 +1,123 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+# SPDX-License-Identifier: BSL-1.0
+# gap_sweep.sh [--seed S] [--ops op1,op2,...] [--bands small,medium,large]
+# [--pin "taskset -c 2"]
+#
+# Phase 2 size grid of the GMP gap analysis. Per op and shape the rows auto/inplace/kernel/gmp/gmpz (whichever apply)
+# run back to back in one process. Small and medium shapes use the harness defaults (9 rounds of 20 ms), the large band
+# uses --rounds 3, gcd 65536 uses --rounds 1. Output: one CSV (op,path,la,lb,reps,rounds,median_ns,min_ns,
+# round_ms_actual) with the '#const' line kept once at the top, after '# gap_sweep' comment lines.
+# Works on macOS bash 3.2 and Ubuntu bash. --bands is for partial runs (default all three).
+# Ops: add sub shl shr cmp mul sqr divrem tochars fromchars gcd (default all).
+set -eu
+
+if [ "$#" -lt 2 ]; then
+ echo "usage: $0 [--seed S] [--ops a,b] [--bands small,medium,large] [--pin \"cmd\"]" >&2
+ exit 2
+fi
+BIN=$1
+OUT=$2
+shift 2
+SEED=1
+OPS=add,sub,shl,shr,cmp,mul,sqr,divrem,tochars,fromchars,gcd
+BANDS=small,medium,large
+PIN=""
+while [ "$#" -gt 0 ]; do
+ case "$1" in
+ --seed) SEED=$2; shift 2 ;;
+ --ops) OPS=$2; shift 2 ;;
+ --bands) BANDS=$2; shift 2 ;;
+ --pin) PIN=$2; shift 2 ;;
+ *) echo "gap_sweep.sh: unknown option $1" >&2; exit 2 ;;
+ esac
+done
+[ -x "$BIN" ] || { echo "gap_sweep.sh: not executable: $BIN" >&2; exit 2; }
+
+want() { case ",$OPS," in *",$1,"*) return 0 ;; esac; return 1; }
+band() { case ",$BANDS," in *",$1,"*) return 0 ;; esac; return 1; }
+
+TMP=$(mktemp "${TMPDIR:-/tmp}/gap_sweep.XXXXXX")
+trap 'rm -f "$TMP"' EXIT
+
+if [ ! -s "$OUT" ]; then
+ {
+ echo "# gap_sweep date $(date -u +%Y-%m-%dT%H:%M:%SZ) host $(hostname) $(uname -sm)"
+ echo "# gap_sweep bin $BIN seed=$SEED ops=$OPS bands=$BANDS pin=$PIN"
+ } >"$OUT"
+ first=1
+else
+ first=0
+fi
+
+# run ...
+run() {
+ op=$1
+ rows=$2
+ rounds=$3
+ shift 3
+ echo "[gap_sweep] $(date +%H:%M:%S) $op rows=$rows rounds=$rounds: $*" >&2
+ # shellcheck disable=SC2086
+ $PIN "$BIN" "$op" --rows "$rows" --rounds "$rounds" --seed "$SEED" "$@" >"$TMP" || {
+ echo "gap_sweep.sh: $op failed (shapes: $*)" >&2
+ exit 1
+ }
+ if [ "$first" = 1 ]; then
+ grep -m1 '^#const' "$TMP" >>"$OUT"
+ grep -m1 '^op,' "$TMP" >>"$OUT"
+ first=0
+ fi
+ grep -v '^#' "$TMP" | grep -v '^op,' >>"$OUT" || true
+}
+
+# sweep (a band with no shapes is "-")
+sweep() {
+ op=$1
+ rows=$2
+ if band small && [ "$3" != "-" ]; then run "$op" "$rows" 9 $3; fi
+ if band medium && [ "$4" != "-" ]; then run "$op" "$rows" 9 $4; fi
+ if band large && [ "$5" != "-" ]; then run "$op" "$rows" 3 $5; fi
+}
+
+if want add; then
+ sweep add auto,inplace,kernel,gmp,gmpz "1x1 2x2 4x4 8x8 16x16" "64x64 256x256 1024x1024 2000x2000 1024x1" \
+ "16384x16384 131072x131072 1048576x1048576"
+fi
+if want sub; then
+ sweep sub auto,inplace,kernel,gmp,gmpz "1x1 2x2 4x4 8x8 16x16" "64x64 256x256 1024x1024 2000x2000 1024x1" \
+ "16384x16384 131072x131072 1048576x1048576"
+fi
+if want shl; then
+ sweep shl auto,inplace,kernel,kernelip,gmp,gmpz "1x13 4x13 16x13" "256x13 2000x13 2000x77" "131072x13 1048576x13"
+fi
+if want shr; then
+ sweep shr auto,inplace,kernel,gmp,gmpz "1x13 4x13 16x13" "256x13 2000x13 2000x77" "131072x13 1048576x13"
+fi
+if want cmp; then
+ sweep cmp auto,kernel,gmp,gmpz "1 4 16" "256 2000" "131072"
+fi
+if want mul; then
+ sweep mul auto,kernel,gmp,gmpz "1x1 2x2 3x3 4x4 6x6 8x8 12x12 16x16" \
+ "32x32 64x64 128x128 256x256 512x512 1000x1000 2000x2000 2000x100" \
+ "4096x4096 16384x16384 65536x65536 262144x262144 1048576x1048576 262144x8192"
+fi
+if want sqr; then
+ sweep sqr auto,kernel,gmp,gmpz "4 16" "64 512 2000" "16384 262144 1048576"
+fi
+if want divrem; then
+ sweep divrem auto,kernel,gmp,gmpz "2x1 8x4 32x16" "128x64 512x256 2000x1000 4000x2000 2000x100 65536x1" \
+ "16384x8192 131072x65536 1048576x524288"
+fi
+if want tochars; then
+ sweep tochars auto,kernel,gmp,gmpz "1x10 4x10 16x10" "64x10 256x10 2000x10" "16384x10 131072x10 262144x10"
+ # Base 16 is a power of two: no span-level kernel row exists.
+ sweep tochars auto,gmp,gmpz "16x16" "2000x16" "131072x16"
+fi
+if want fromchars; then
+ sweep fromchars auto,kernel,gmp,gmpz "4x10 16x10" "64x10 256x10 2000x10" "16384x10 131072x10"
+fi
+if want gcd; then
+ sweep gcd auto,kernel,gmp,gmpz "2x2 4x4 16x16" "64x64 256x256 1024x1024 2000x2000" "16384x16384"
+ if band large; then run gcd auto,kernel,gmp,gmpz 1 65536x65536; fi
+fi
+echo "[gap_sweep] $(date +%H:%M:%S) done: $OUT" >&2
diff --git a/tests/beman/big_int/perf/tools/probe/build_mac.sh b/tests/beman/big_int/perf/tools/probe/build_mac.sh
new file mode 100755
index 00000000..78c9ebb0
--- /dev/null
+++ b/tests/beman/big_int/perf/tools/probe/build_mac.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+# SPDX-License-Identifier: BSL-1.0
+# Build the probe with the system clang++ at -O2 and -O3 against a static GMP.
+# Env: GMP (default $(brew --prefix gmp)/lib/libgmp.a, else /opt/homebrew/lib/libgmp.a), BIN_DIR (default ./probe-bin).
+# Output: $BIN_DIR/probe.mac.O2 and probe.mac.O3.
+set -e
+HERE=$(cd "$(dirname "$0")" && pwd)
+ROOT=$(cd "$HERE/../../../../../.." && pwd)
+BIN_DIR=${BIN_DIR:-$PWD/probe-bin}
+if [ -z "${GMP:-}" ]; then
+ GMP=$(brew --prefix gmp 2>/dev/null || echo /opt/homebrew)/lib/libgmp.a
+fi
+[ -f "$GMP" ] || { echo "missing $GMP (set GMP=/path/to/libgmp.a)"; exit 1; }
+mkdir -p "$BIN_DIR"
+DEFS=""
+for pair in by3c:divexact_by3c bdiv_q_1:bdiv_q_1 divexact_1:divexact_1; do
+ d=${pair%%:*}
+ s=${pair##*:}
+ if nm "$GMP" 2>/dev/null | grep -Eq " T _{2,3}gmpn_$s\$"; then
+ DEFS="$DEFS -DPROBE_HAVE_$(echo "$d" | tr a-z A-Z)"
+ fi
+done
+for O in O2 O3; do
+ # shellcheck disable=SC2086
+ clang++ -std=c++23 -$O $DEFS -I "$ROOT/include" "$HERE/probe.cpp" "$GMP" -o "$BIN_DIR/probe.mac.$O"
+done
+echo "built $BIN_DIR/probe.mac.O2 probe.mac.O3 (defs:$DEFS)"
diff --git a/tests/beman/big_int/perf/tools/probe/build_x64.sh b/tests/beman/big_int/perf/tools/probe/build_x64.sh
new file mode 100755
index 00000000..15d01983
--- /dev/null
+++ b/tests/beman/big_int/perf/tools/probe/build_x64.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+# SPDX-License-Identifier: BSL-1.0
+# Build the probe on x86-64 Linux: each compiler in COMPILERS at -O2 and -O3, -march=native (about 1 minute).
+# Env: GMP (default /usr/lib/x86_64-linux-gnu/libgmp.a; point it at a custom build's libgmp.a if you have one),
+# BIN_DIR (default ./probe-bin), COMPILERS (default "gcc13:g++-13 gcc14:g++-14 clang23:clang++-23"; a missing
+# compiler is skipped). Output: $BIN_DIR/probe..
+set -e
+HERE=$(cd "$(dirname "$0")" && pwd)
+ROOT=$(cd "$HERE/../../../../../.." && pwd)
+GMP=${GMP:-/usr/lib/x86_64-linux-gnu/libgmp.a}
+BIN_DIR=${BIN_DIR:-$PWD/probe-bin}
+COMPILERS=${COMPILERS:-"gcc13:g++-13 gcc14:g++-14 clang23:clang++-23"}
+mkdir -p "$BIN_DIR"
+[ -f "$GMP" ] || { echo "missing $GMP"; exit 1; }
+DEFS=""
+for pair in by3c:divexact_by3c bdiv_q_1:bdiv_q_1 divexact_1:divexact_1; do
+ d=${pair%%:*}; s=${pair##*:}
+ if nm "$GMP" 2>/dev/null | grep -Eq " T _{2,3}gmpn_$s\$"; then
+ DEFS="$DEFS -DPROBE_HAVE_$(echo $d | tr a-z A-Z)"
+ fi
+done
+echo "gmp defs:$DEFS"
+for pair in $COMPILERS; do
+ tag=${pair%%:*}; cxx=${pair##*:}
+ if ! command -v "$cxx" >/dev/null 2>&1; then echo "SKIP $tag ($cxx not found)"; continue; fi
+ for O in O2 O3; do
+ $cxx -std=c++23 -$O -march=native -DNDEBUG $DEFS -no-pie -I "$ROOT/include" "$HERE/probe.cpp" "$GMP" \
+ -o "$BIN_DIR/probe.$tag.$O"
+ echo "built probe.$tag.$O ($($cxx --version | head -1))"
+ done
+done
diff --git a/tests/beman/big_int/perf/tools/probe/probe.cpp b/tests/beman/big_int/perf/tools/probe/probe.cpp
new file mode 100644
index 00000000..2aeca2ee
--- /dev/null
+++ b/tests/beman/big_int/perf/tools/probe/probe.cpp
@@ -0,0 +1,563 @@
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+// SPDX-License-Identifier: BSL-1.0
+
+// "Do we need asm for linear kernels?" headroom probe: library span kernels versus loop variants and GMP's mpn.
+// Standalone program, not part of the CMake build; build with build_mac.sh / build_x64.sh in this directory.
+// Needs only `-I include` (the headers fall back to their own feature detection when no generated config header is on
+// the include path).
+//
+// probe --list list kernel:variant names
+// probe --variant K:V --n N --reps R run one variant R times (for perf stat)
+// probe --all [--rounds 7] [--ms 20] check everything, then CSV kernel,variant,n,ns_per_limb
+// probe --check correctness checks only
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// ---- GMP (hand-declared; mp_limb_t = unsigned long, mp_size_t = long) -------------------------
+using gl = unsigned long;
+extern "C" {
+gl __gmpn_add_n(gl*, const gl*, const gl*, long);
+gl __gmpn_sub_n(gl*, const gl*, const gl*, long);
+gl __gmpn_lshift(gl*, const gl*, long, unsigned);
+gl __gmpn_submul_1(gl*, const gl*, long, gl);
+gl __gmpn_mul_1(gl*, const gl*, long, gl);
+gl __gmpn_divrem_1(gl*, long, const gl*, long, gl);
+#ifdef PROBE_HAVE_BY3C
+gl __gmpn_divexact_by3c(gl*, const gl*, long, gl);
+#endif
+#ifdef PROBE_HAVE_BDIV_Q_1
+gl __gmpn_bdiv_q_1(gl*, const gl*, long, gl);
+#endif
+#ifdef PROBE_HAVE_DIVEXACT_1
+void __gmpn_divexact_1(gl*, const gl*, long, gl);
+#endif
+}
+
+namespace dt = BEMAN_BIG_INT_NAMESPACE::detail;
+using limb = unsigned long long;
+static_assert(sizeof(limb) == sizeof(gl) && std::is_same_v);
+
+inline static gl* G(limb* p) { return reinterpret_cast(p); }
+inline static const gl* G(const limb* p) { return reinterpret_cast(p); }
+
+#define NOINL __attribute__((noinline))
+
+// ---- test buffers ------------------------------------------------------------------------------
+constexpr std::size_t kMaxN = 512;
+constexpr int kSets = 3;
+constexpr std::size_t kPad = kMaxN + 8;
+
+struct Buf {
+ alignas(64) limb a[kPad];
+ alignas(64) limb b[kPad];
+ alignas(64) limb r[kPad];
+};
+static Buf g_bufs[kSets];
+
+struct Args {
+ Buf& B;
+ std::size_t n;
+ limb extra; // multiplier / divisor / shift count
+};
+using Fn = limb (*)(const Args&);
+
+static limb splitmix(std::uint64_t& s) {
+ std::uint64_t z = (s += 0x9e3779b97f4a7c15ULL);
+ z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL;
+ z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL;
+ return z ^ (z >> 31);
+}
+
+inline static void escape(limb v) { asm volatile("" : : "r"(v) : "memory"); }
+
+// ---- variants ----------------------------------------------------------------------------------
+using cspan = std::span;
+using mspan = std::span;
+
+// add_n -----------------------------------------------------------------------------------------
+NOINL static limb add_lib(const Args& x) {
+ return dt::add_unsigned_spans(mspan{x.B.r, x.n}, cspan{x.B.a, x.n}, cspan{x.B.b, x.n});
+}
+NOINL static limb add_loop(const Args& x) {
+ bool carry = false;
+ for (std::size_t i = 0; i < x.n; ++i) {
+ const auto [v, c] = dt::carrying_add(x.B.a[i], x.B.b[i], carry);
+ x.B.r[i] = v;
+ carry = c;
+ }
+ return carry;
+}
+#if __has_builtin(__builtin_addcll)
+NOINL static limb add_addcll4(const Args& x) {
+ const limb *a = x.B.a, *b = x.B.b;
+ limb* r = x.B.r;
+ limb c = 0;
+ std::size_t i = 0;
+ for (; i + 4 <= x.n; i += 4) {
+ r[i] = __builtin_addcll(a[i], b[i], c, &c);
+ r[i + 1] = __builtin_addcll(a[i + 1], b[i + 1], c, &c);
+ r[i + 2] = __builtin_addcll(a[i + 2], b[i + 2], c, &c);
+ r[i + 3] = __builtin_addcll(a[i + 3], b[i + 3], c, &c);
+ }
+ for (; i < x.n; ++i) {
+ r[i] = __builtin_addcll(a[i], b[i], c, &c);
+ }
+ return c;
+}
+ #define PROBE_HAVE_ADDCLL4 1
+#endif
+#if defined(__x86_64__)
+// Tail (n % 4 limbs) in C, then a 4x unrolled adc loop; dec and lea leave CF intact.
+NOINL static limb add_asm4(const Args& x) {
+ const limb *a = x.B.a, *b = x.B.b;
+ limb* r = x.B.r;
+ limb c = 0;
+ std::size_t i = 0;
+ for (std::size_t t = x.n & 3; i < t; ++i) {
+ const auto [v, cc] = dt::carrying_add(a[i], b[i], static_cast(c));
+ r[i] = v;
+ c = cc;
+ }
+ std::size_t cnt = (x.n - i) >> 2;
+ if (cnt != 0) {
+ asm volatile("neg %[c]\n\t"
+ ".p2align 4\n"
+ "1:\n\t"
+ "mov (%[a],%[i],8), %%r8\n\t"
+ "mov 8(%[a],%[i],8), %%r9\n\t"
+ "mov 16(%[a],%[i],8), %%r10\n\t"
+ "mov 24(%[a],%[i],8), %%r11\n\t"
+ "adc (%[b],%[i],8), %%r8\n\t"
+ "adc 8(%[b],%[i],8), %%r9\n\t"
+ "adc 16(%[b],%[i],8), %%r10\n\t"
+ "adc 24(%[b],%[i],8), %%r11\n\t"
+ "mov %%r8, (%[r],%[i],8)\n\t"
+ "mov %%r9, 8(%[r],%[i],8)\n\t"
+ "mov %%r10, 16(%[r],%[i],8)\n\t"
+ "mov %%r11, 24(%[r],%[i],8)\n\t"
+ "lea 4(%[i]), %[i]\n\t"
+ "dec %[cnt]\n\t"
+ "jnz 1b\n\t"
+ "sbb %[c], %[c]\n\t"
+ "neg %[c]\n"
+ : [c] "+r"(c), [i] "+r"(i), [cnt] "+r"(cnt)
+ : [a] "r"(a), [b] "r"(b), [r] "r"(r)
+ : "r8", "r9", "r10", "r11", "cc", "memory");
+ }
+ return c;
+}
+ #define PROBE_HAVE_ASM4 1
+#endif
+NOINL static limb add_gmp(const Args& x) { return __gmpn_add_n(G(x.B.r), G(x.B.a), G(x.B.b), static_cast(x.n)); }
+
+// sub_n -----------------------------------------------------------------------------------------
+// Library kernel asserts a >= b and returns a trimmed size, so the wrapper reports borrow 0.
+NOINL static limb sub_lib(const Args& x) {
+ escape(dt::subtract_unsigned_spans(mspan{x.B.r, x.n}, cspan{x.B.a, x.n}, cspan{x.B.b, x.n}));
+ return 0;
+}
+NOINL static limb sub_loop(const Args& x) {
+ bool borrow = false;
+ for (std::size_t i = 0; i < x.n; ++i) {
+ const auto [v, c] = dt::borrowing_sub(x.B.a[i], x.B.b[i], borrow);
+ x.B.r[i] = v;
+ borrow = c;
+ }
+ return borrow;
+}
+NOINL static limb sub_gmp(const Args& x) { return __gmpn_sub_n(G(x.B.r), G(x.B.a), G(x.B.b), static_cast(x.n)); }
+
+// lshift: x.extra = bit count (1..63). r[0..n) = a << cnt; returns bits shifted out.
+NOINL static limb lsh_lib(const Args& x) {
+ std::memcpy(x.B.r, x.B.a, x.n * sizeof(limb));
+ x.B.r[x.n] = 0;
+ escape(dt::shift_left_n(mspan{x.B.r, x.n + 1}, x.n, static_cast(x.extra)));
+ return x.B.r[x.n];
+}
+NOINL static limb lsh_gmp(const Args& x) {
+ return __gmpn_lshift(G(x.B.r), G(x.B.a), static_cast(x.n), static_cast(x.extra));
+}
+NOINL static limb lsh_gmp_copy(const Args& x) {
+ std::memcpy(x.B.r, x.B.a, x.n * sizeof(limb));
+ return __gmpn_lshift(G(x.B.r), G(x.B.r), static_cast(x.n), static_cast(x.extra));
+}
+
+// submul_1: r[0..n) -= a[0..n) * extra; returns borrow limb.
+NOINL static limb smul_lib(const Args& x) {
+ return dt::submul_single_limb(mspan{x.B.r, x.n}, cspan{x.B.a, x.n}, x.extra);
+}
+NOINL static limb smul_gmp(const Args& x) {
+ return __gmpn_submul_1(G(x.B.r), G(x.B.a), static_cast(x.n), x.extra);
+}
+
+// mul_1: r[0..n) = a * extra; returns carry limb (library: r[n]).
+NOINL static limb mul1_lib(const Args& x) {
+ x.B.r[x.n] = 0;
+ escape(dt::multiply_single_limb(mspan{x.B.r, x.n + 1}, cspan{x.B.a, x.n}, x.extra));
+ return x.B.r[x.n];
+}
+NOINL static limb mul1_gmp(const Args& x) { return __gmpn_mul_1(G(x.B.r), G(x.B.a), static_cast(x.n), x.extra); }
+
+// divrem_1 / divexact: q = a / extra, returns remainder.
+NOINL static limb div_lib(const Args& x) {
+ return dt::divide_unsigned_short(mspan{x.B.r, x.n}, cspan{x.B.a, x.n}, x.extra);
+}
+NOINL static limb div_gmp(const Args& x) {
+ return __gmpn_divrem_1(G(x.B.r), 0, G(x.B.a), static_cast(x.n), x.extra);
+}
+#ifdef PROBE_HAVE_BY3C
+NOINL static limb by3c_gmp(const Args& x) {
+ return __gmpn_divexact_by3c(G(x.B.r), G(x.B.a), static_cast(x.n), 0);
+}
+#endif
+#ifdef PROBE_HAVE_BDIV_Q_1
+NOINL static limb bdivq1_gmp(const Args& x) {
+ __gmpn_bdiv_q_1(G(x.B.r), G(x.B.a), static_cast(x.n), x.extra);
+ return 0;
+}
+#endif
+#ifdef PROBE_HAVE_DIVEXACT_1
+NOINL static limb dexact1_gmp(const Args& x) {
+ __gmpn_divexact_1(G(x.B.r), G(x.B.a), static_cast(x.n), x.extra);
+ return 0;
+}
+#endif
+
+// ---- registry ----------------------------------------------------------------------------------
+enum class Prep { plain, sub_ge, divisible3, nonzero_top };
+struct Variant {
+ const char* name;
+ Fn fn;
+ bool ref; // reference (GMP) for the kernel's correctness check
+};
+struct Kernel {
+ const char* name;
+ limb extra;
+ Prep prep;
+ bool r_in; // r holds an input (submul): r is reset to r0 before each check run
+ std::vector v;
+};
+
+static std::vector make_kernels() {
+ std::vector k;
+ {
+ Kernel a{"add_n", 0, Prep::plain, false, {}};
+ a.v.push_back({"lib_add_unsigned_spans", add_lib, false});
+ a.v.push_back({"loop_carrying_add", add_loop, false});
+#ifdef PROBE_HAVE_ADDCLL4
+ a.v.push_back({"addcll_x4", add_addcll4, false});
+#endif
+#ifdef PROBE_HAVE_ASM4
+ a.v.push_back({"asm_adc_x4", add_asm4, false});
+#endif
+ a.v.push_back({"gmp", add_gmp, true});
+ k.push_back(a);
+ }
+ {
+ Kernel s{"sub_n", 0, Prep::sub_ge, false, {}};
+ s.v.push_back({"lib_subtract_unsigned_spans", sub_lib, false});
+ s.v.push_back({"loop_borrowing_sub", sub_loop, false});
+ s.v.push_back({"gmp", sub_gmp, true});
+ k.push_back(s);
+ }
+ for (const limb cnt : {limb{13}, limb{1}, limb{63}}) {
+ const char* nm = cnt == 13 ? "lshift13" : cnt == 1 ? "lshift1" : "lshift63";
+ Kernel l{nm, cnt, Prep::plain, false, {}};
+ l.v.push_back({"lib_copy_shift_left_n", lsh_lib, false});
+ l.v.push_back({"gmp_copy_inplace", lsh_gmp_copy, false});
+ l.v.push_back({"gmp", lsh_gmp, true});
+ k.push_back(l);
+ }
+ {
+ Kernel s{"submul_1", 0x9e3779b97f4a7c15ULL, Prep::plain, true, {}};
+ s.v.push_back({"lib_submul_single_limb", smul_lib, false});
+ s.v.push_back({"gmp", smul_gmp, true});
+ k.push_back(s);
+ Kernel m{"mul_1", 0x9e3779b97f4a7c15ULL, Prep::plain, false, {}};
+ m.v.push_back({"lib_multiply_single_limb", mul1_lib, false});
+ m.v.push_back({"gmp", mul1_gmp, true});
+ k.push_back(m);
+ }
+ {
+ Kernel d{"divrem_1_norm", 0xF123456789ABCDEFULL, Prep::plain, false, {}};
+ d.v.push_back({"lib_divide_unsigned_short", div_lib, false});
+ d.v.push_back({"gmp", div_gmp, true});
+ k.push_back(d);
+ Kernel u{"divrem_1_unnorm", 1000000007ULL, Prep::plain, false, {}};
+ u.v.push_back({"lib_divide_unsigned_short", div_lib, false});
+ u.v.push_back({"gmp", div_gmp, true});
+ k.push_back(u);
+ }
+ {
+ Kernel e{"divexact_3", 3, Prep::divisible3, false, {}};
+ e.v.push_back({"lib_divide_unsigned_short", div_lib, false});
+ e.v.push_back({"gmp_divrem_1", div_gmp, true});
+#ifdef PROBE_HAVE_BY3C
+ e.v.push_back({"gmp_divexact_by3c", by3c_gmp, false});
+#endif
+#ifdef PROBE_HAVE_BDIV_Q_1
+ e.v.push_back({"gmp_bdiv_q_1", bdivq1_gmp, false});
+#endif
+#ifdef PROBE_HAVE_DIVEXACT_1
+ e.v.push_back({"gmp_divexact_1", dexact1_gmp, false});
+#endif
+ k.push_back(e);
+ }
+ return k;
+}
+
+static void fill(Buf& B, std::uint64_t seed, Prep prep) {
+ std::uint64_t s = seed;
+ for (std::size_t i = 0; i < kPad; ++i) {
+ B.a[i] = splitmix(s);
+ B.b[i] = splitmix(s);
+ B.r[i] = splitmix(s);
+ }
+ // Give sub_n a >= b at every n: top limb of a above b's. For n >= 1 any prefix n
+ // needs its own top limb ordering, so make a[i] > b[i] limbwise (no borrows at all
+ // is too easy), so instead force b[i] = a[i] >> 1 on odd i and keep random on even i.
+ if (prep == Prep::sub_ge) {
+ for (std::size_t i = 0; i < kPad; ++i) {
+ if (i % 2 == 1) {
+ B.b[i] = B.a[i] >> 1; // forces a[i] > b[i] on odd limbs
+ }
+ if (i % 5 == 0) {
+ B.b[i] = B.a[i]; // runs of equal limbs exercise borrow chains
+ }
+ }
+ for (std::size_t i = 0; i < kPad; ++i) {
+ if (i % 7 == 3) {
+ B.b[i] = B.a[i] + 1; // introduces borrows
+ }
+ }
+ // Make every prefix satisfy a >= b by making the final limb of any prefix larger:
+ // enforced below per-n by the checker through a_top adjustments (see check_one).
+ }
+ if (prep == Prep::divisible3) {
+ for (std::size_t i = 0; i < kPad; ++i) {
+ B.a[i] = splitmix(s);
+ }
+ }
+}
+
+// Prepare the n-limb prefix so the kernel's preconditions hold.
+static void shape_for_n(Buf& B, std::size_t n, Prep prep) {
+ if (prep == Prep::sub_ge) {
+ B.b[n - 1] = B.a[n - 1] >> 1; // top limb strictly smaller when a[n-1] != 0
+ B.a[n - 1] |= 1ULL << 63;
+ B.b[n - 1] &= ~(1ULL << 63);
+ } else if (prep == Prep::divisible3) {
+ B.a[n - 1] >>= 2;
+ const gl cy = __gmpn_mul_1(G(B.a), G(B.a), static_cast(n), 3);
+ if (cy != 0) {
+ std::fprintf(stderr, "divexact prep overflow\n");
+ std::abort();
+ }
+ }
+}
+
+[[noreturn]] static void die(const char* k, const char* v, std::size_t n, limb extra, const char* what) {
+ std::fprintf(stderr, "MISMATCH %s:%s n=%zu extra=%llu: %s\n", k, v, n, extra, what);
+ std::abort();
+}
+
+static void check_kernel(const Kernel& k, const std::vector& ns, const std::vector& extras) {
+ const Variant* ref = nullptr;
+ for (const auto& v : k.v) {
+ if (v.ref) {
+ ref = &v;
+ }
+ }
+ static Buf Bref, Bv, Binit;
+ for (const std::size_t n : ns) {
+ for (const limb ex : extras) {
+ for (std::uint64_t seed = 1; seed <= 3; ++seed) {
+ fill(Binit, seed * 7919 + n, k.prep);
+ shape_for_n(Binit, n, k.prep);
+ if (seed == 3) { // extreme limbs: all ones
+ for (std::size_t i = 0; i < n && k.prep == Prep::plain; ++i) {
+ Binit.a[i] = ~0ULL;
+ Binit.b[i] = (i & 1) ? ~0ULL : 0;
+ Binit.r[i] = ~0ULL;
+ }
+ }
+ Bref = Binit;
+ const limb want = ref->fn({Bref, n, ex});
+ for (const auto& v : k.v) {
+ Bv = Binit;
+ const limb g = v.fn({Bv, n, ex});
+ if (g != want) {
+ die(k.name, v.name, n, ex, "scalar result");
+ }
+ if (std::memcmp(Bv.r, Bref.r, n * sizeof(limb)) != 0) {
+ die(k.name, v.name, n, ex, "limbs");
+ }
+ if (std::memcmp(Bv.a, Binit.a, n * sizeof(limb)) != 0) {
+ die(k.name, v.name, n, ex, "input a clobbered");
+ }
+ }
+ }
+ }
+ }
+}
+
+static void check_all(const std::vector& ks) {
+ const std::vector ns = {1, 2, 3, 4, 5, 6, 7, 8, 9, 15, 16, 17, 31, 64, 65, 255, 511, 512};
+ for (const auto& k : ks) {
+ std::vector extras = {k.extra};
+ std::vector kn = ns;
+ if (std::strcmp(k.name, "lshift13") == 0) {
+ extras.clear();
+ for (limb c = 1; c < 64; ++c) {
+ extras.push_back(c);
+ }
+ }
+ if (std::strncmp(k.name, "divrem_1_", 9) == 0) {
+ extras = {k.extra, 1ULL << 63, 3, 0xFFFFFFFFFFFFFFFFULL, 12345, (1ULL << 32) + 1};
+ }
+ if (std::strcmp(k.name, "submul_1") == 0 || std::strcmp(k.name, "mul_1") == 0) {
+ extras = {k.extra, 0, 1, ~0ULL, 1ULL << 63};
+ }
+ check_kernel(k, kn, extras);
+ }
+ std::fprintf(stderr, "correctness: all kernels match GMP\n");
+}
+
+// ---- timing ------------------------------------------------------------------------------------
+static void init_bufs(const Kernel& k, std::size_t n) {
+ for (int s = 0; s < kSets; ++s) {
+ fill(g_bufs[s], 100 + s, k.prep);
+ shape_for_n(g_bufs[s], n, k.prep);
+ }
+}
+
+static limb run_reps(const Variant& v, const Kernel& k, std::size_t n, std::uint64_t reps) {
+ limb acc = 0;
+ int s = 0;
+ for (std::uint64_t i = 0; i < reps; ++i) {
+ acc += v.fn({g_bufs[s], n, k.extra});
+ escape(acc);
+ if (++s == kSets) {
+ s = 0;
+ }
+ }
+ return acc;
+}
+
+static double ns_now() {
+ return std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count();
+}
+
+static double time_variant(const Variant& v, const Kernel& k, std::size_t n, int rounds, double ms) {
+ init_bufs(k, n);
+ std::uint64_t reps = 16;
+ for (;;) { // calibrate to ~ms per round
+ const double t0 = ns_now();
+ run_reps(v, k, n, reps);
+ const double dt_ns = ns_now() - t0;
+ if (dt_ns >= ms * 1e6 * 0.5 || reps > (1ULL << 40)) {
+ reps = static_cast(static_cast(reps) * (ms * 1e6) / (dt_ns > 1 ? dt_ns : 1));
+ if (reps < 1) {
+ reps = 1;
+ }
+ break;
+ }
+ reps *= 4;
+ }
+ double best = 1e300;
+ for (int r = 0; r < rounds; ++r) {
+ const double t0 = ns_now();
+ run_reps(v, k, n, reps);
+ const double t = ns_now() - t0;
+ if (t < best) {
+ best = t;
+ }
+ }
+ return best / (static_cast(reps) * static_cast(n));
+}
+
+int main(int argc, char** argv) {
+ std::string var;
+ std::size_t n = 0;
+ std::uint64_t reps = 0;
+ int rounds = 7;
+ double ms = 20;
+ bool all = false, list = false, check = false;
+ for (int i = 1; i < argc; ++i) {
+ const std::string a = argv[i];
+ if (a == "--variant" && i + 1 < argc) {
+ var = argv[++i];
+ } else if (a == "--n" && i + 1 < argc) {
+ n = std::strtoull(argv[++i], nullptr, 10);
+ } else if (a == "--reps" && i + 1 < argc) {
+ reps = std::strtoull(argv[++i], nullptr, 10);
+ } else if (a == "--rounds" && i + 1 < argc) {
+ rounds = std::atoi(argv[++i]);
+ } else if (a == "--ms" && i + 1 < argc) {
+ ms = std::atof(argv[++i]);
+ } else if (a == "--all") {
+ all = true;
+ } else if (a == "--list") {
+ list = true;
+ } else if (a == "--check") {
+ check = true;
+ } else {
+ std::fprintf(stderr, "bad arg %s\n", a.c_str());
+ return 2;
+ }
+ }
+ const auto ks = make_kernels();
+ if (list) {
+ for (const auto& k : ks) {
+ for (const auto& v : k.v) {
+ std::printf("%s:%s\n", k.name, v.name);
+ }
+ }
+ return 0;
+ }
+ if (var.empty()) {
+ check_all(ks); // verify before timing (--variant runs skip it: perf must see only the kernel)
+ }
+ if (check) {
+ return 0;
+ }
+ if (all) {
+ std::printf("kernel,variant,n,ns_per_limb\n");
+ for (const auto& k : ks) {
+ for (const std::size_t nn : {std::size_t{8}, std::size_t{64}, std::size_t{512}}) {
+ for (const auto& v : k.v) {
+ std::printf("%s,%s,%zu,%.4f\n", k.name, v.name, nn, time_variant(v, k, nn, rounds, ms));
+ std::fflush(stdout);
+ }
+ }
+ }
+ return 0;
+ }
+ if (!var.empty() && n != 0 && reps != 0) {
+ for (const auto& k : ks) {
+ for (const auto& v : k.v) {
+ if (var == std::string(k.name) + ":" + v.name) {
+ init_bufs(k, n);
+ const limb acc = run_reps(v, k, n, reps);
+ std::printf(
+ "%s n=%zu reps=%llu acc=%llu\n", var.c_str(), n, static_cast(reps), acc);
+ return 0;
+ }
+ }
+ }
+ std::fprintf(stderr, "unknown variant %s\n", var.c_str());
+ return 2;
+ }
+ std::fprintf(stderr, "usage: probe --list | --check | --all | --variant K:V --n N --reps R\n");
+ return 2;
+}
diff --git a/tests/beman/big_int/perf/tools/probe/run_mac.sh b/tests/beman/big_int/perf/tools/probe/run_mac.sh
new file mode 100755
index 00000000..e917eb98
--- /dev/null
+++ b/tests/beman/big_int/perf/tools/probe/run_mac.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+# SPDX-License-Identifier: BSL-1.0
+# Time the probe on a quiet Mac (about 1 minute). Builds it first if needed.
+# Env: BIN_DIR (default ./probe-bin), OUT (default ./probe_m4.csv), GHZ_NOTE (default 4.47, only written to the header).
+# Output: CSV opt,kernel,variant,n,ns_per_limb; c/l ~= ns_per_limb * GHz (frequency is not pinned).
+set -e
+HERE=$(cd "$(dirname "$0")" && pwd)
+BIN_DIR=${BIN_DIR:-$PWD/probe-bin}
+OUT=${OUT:-$PWD/probe_m4.csv}
+export BIN_DIR
+[ -x "$BIN_DIR/probe.mac.O2" ] || "$HERE/build_mac.sh"
+{
+ echo "# c/l ~= ns_per_limb * ${GHZ_NOTE:-4.47} (approximate)"
+ echo "opt,kernel,variant,n,ns_per_limb"
+ for O in O2 O3; do
+ "$BIN_DIR/probe.mac.$O" --all | tail -n +2 | awk -F, -v o="$O" '{printf "%s,%s,%s,%s,%s\n", o, $1, $2, $3, $4}'
+ done
+} >"$OUT"
+echo "wrote $OUT"
diff --git a/tests/beman/big_int/perf/tools/probe/run_x64.sh b/tests/beman/big_int/perf/tools/probe/run_x64.sh
new file mode 100755
index 00000000..755f7065
--- /dev/null
+++ b/tests/beman/big_int/perf/tools/probe/run_x64.sh
@@ -0,0 +1,54 @@
+#!/bin/sh
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+# SPDX-License-Identifier: BSL-1.0
+# Run the probe on x86-64 Linux, pinned to one core, with perf stat. Needs build_x64.sh first (about 4-6 minutes).
+# Env: BIN_DIR (default ./probe-bin), OUT (default ./probe-results), CPU (default 2), LIMBS (default 100000000).
+# Outputs in $OUT:
+# probe_x64.csv compiler,opt,kernel,variant,n,cycles_per_limb,ipc
+# probe_x64_ns.csv compiler,opt,kernel,variant,n,ns_per_limb (internal steady_clock loop)
+# probe_asm/ objdump listings of the add_n loops
+# c/l is a difference of two runs (2R and R reps) so process startup cancels:
+# c/l = (cycles(2R) - cycles(R)) / (R * n); ipc likewise from instructions.
+set -e
+BIN_DIR=${BIN_DIR:-$PWD/probe-bin}
+OUT=${OUT:-$PWD/probe-results}
+CPU=${CPU:-2}
+LIMBS=${LIMBS:-100000000} # limbs processed in the R-run (~0.05-0.6 s per run)
+mkdir -p "$OUT/probe_asm"
+CSV="$OUT/probe_x64.csv"; NS="$OUT/probe_x64_ns.csv"
+echo "compiler,opt,kernel,variant,n,cycles_per_limb,ipc" > "$CSV"
+echo "compiler,opt,kernel,variant,n,ns_per_limb" > "$NS"
+
+stat() { # stat BIN VARIANT N REPS -> "cycles instructions"
+ taskset -c "$CPU" perf stat -x, -e cycles:u,instructions:u "$1" --variant "$2" --n "$3" --reps "$4" 2>&1 >/dev/null |
+ awk -F, '/cycles/ {c=$1} /instructions/ {i=$1} END {print c, i}'
+}
+
+for bin in "$BIN_DIR"/probe.*; do
+ base=$(basename "$bin"); tag=${base#probe.}; comp=${tag%%.*}; opt=${tag#*.}
+ "$bin" --check || { echo "CHECK FAILED $base"; exit 1; }
+ for v in $("$bin" --list); do
+ for n in 8 64 512; do
+ R=$((LIMBS / n))
+ set -- $(stat "$bin" "$v" "$n" "$R"); c1=$1; i1=$2
+ set -- $(stat "$bin" "$v" "$n" $((2 * R))); c2=$1; i2=$2
+ kern=${v%%:*}; var=${v#*:}
+ awk -v c1="$c1" -v c2="$c2" -v i1="$i1" -v i2="$i2" -v R="$R" -v n="$n" -v comp="$comp" -v opt="$opt" \
+ -v k="$kern" -v v="$var" 'BEGIN {
+ dc = c2 - c1; di = i2 - i1;
+ printf "%s,%s,%s,%s,%d,%.4f,%.3f\n", comp, opt, k, v, n, dc / (R * n), (dc > 0 ? di / dc : 0) }' >> "$CSV"
+ done
+ done
+ taskset -c "$CPU" "$bin" --all | tail -n +2 | awk -F, -v comp="$comp" -v opt="$opt" \
+ '{printf "%s,%s,%s,%s,%s,%s\n", comp, opt, $1, $2, $3, $4}' >> "$NS"
+ # hot-loop listings for the add_n variants
+ objdump -d -C --no-show-raw-insn "$bin" > "$OUT/probe_asm/.full.$base.txt"
+ for f in add_lib add_loop add_addcll4 add_asm4; do
+ awk -v f="$f" '/^[0-9a-f]+ <.*>:$/ {p = ($0 ~ "^[0-9a-f]+ <" f "\\(")} p {print}' \
+ "$OUT/probe_asm/.full.$base.txt" > "$OUT/probe_asm/$base.$f.txt"
+ [ -s "$OUT/probe_asm/$base.$f.txt" ] || echo "not built ($f absent from $base)" > "$OUT/probe_asm/$base.$f.txt"
+ done
+ rm -f "$OUT/probe_asm/.full.$base.txt"
+ echo "done $base"
+done
+echo "wrote $CSV $NS $OUT/probe_asm/"
diff --git a/tests/beman/big_int/perf/tools/probe_tables.py b/tests/beman/big_int/perf/tools/probe_tables.py
new file mode 100755
index 00000000..7f2bfa97
--- /dev/null
+++ b/tests/beman/big_int/perf/tools/probe_tables.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+# SPDX-License-Identifier: BSL-1.0
+# probe_tables.py [n] [opt]: x64 cycles/limb per compiler + M4 ns/limb*4.47, markdown
+import csv, sys, collections
+x = [r for r in csv.DictReader(l for l in open(sys.argv[1]) if not l.startswith("#"))]
+m = [r for r in csv.DictReader(l for l in open(sys.argv[2]) if not l.startswith("#"))]
+n = sys.argv[3] if len(sys.argv) > 3 else "64"
+opt = sys.argv[4] if len(sys.argv) > 4 else "O2"
+X = {(r["compiler"], r["kernel"], r["variant"]): float(r["cycles_per_limb"]) for r in x if r["n"] == n and r["opt"] == opt}
+M = {(r["kernel"], r["variant"]): float(r["ns_per_limb"]) * 4.47 for r in m if r["n"] == n and r["opt"] == opt}
+keys = []
+for r in x:
+ k = (r["kernel"], r["variant"])
+ if k not in keys: keys.append(k)
+print(f"| kernel | variant | gcc13 c/l | gcc14 c/l | clang23 c/l | M4 c/l | gcc14 / GMP | M4 / GMP |")
+print("|---|---|---:|---:|---:|---:|---:|---:|")
+for k, v in keys:
+ g = X.get(("gcc14", k, "gmp"))
+ mg = M.get((k, "gmp"))
+ def c(comp):
+ z = X.get((comp, k, v)); return "-" if z is None else f"{z:.2f}"
+ mv = M.get((k, v))
+ r1 = X.get(("gcc14", k, v));
+ print(f"| {k} | {v} | {c('gcc13')} | {c('gcc14')} | {c('clang23')} | {'-' if mv is None else f'{mv:.2f}'} | "
+ f"{'-' if not (g and r1) else f'{r1/g:.2f}'} | {'-' if not (mg and mv) else f'{mv/mg:.2f}'} |")
diff --git a/tests/beman/big_int/perf/tools/prof.sh b/tests/beman/big_int/perf/tools/prof.sh
new file mode 100755
index 00000000..634a94db
--- /dev/null
+++ b/tests/beman/big_int/perf/tools/prof.sh
@@ -0,0 +1,61 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+# SPDX-License-Identifier: BSL-1.0
+# prof.sh [filter-substring]
+# Records LBR call-stack profiles (perf, Linux x86-64, kernel.perf_event_paranoid <= 1) of big_int (auto) and GMP
+# (gmpz) for the shapes in LIST, folds them and renders flame graphs into /__.{data,out,
+# raw.folded,folded,svg}. Then bucket with: classify.py > buckets.md
+# Env: FLAMEGRAPH_DIR (default $HOME/FlameGraph, a clone of github.com/brendangregg/FlameGraph),
+# PIN (default "taskset -c 2"), MINW (flamegraph.pl --minwidth, default 0.5), LABEL (title suffix, default empty),
+# SEED (default 1).
+set -u
+BIN=$(cd "$(dirname "${1:?usage: prof.sh [filter]}")" && pwd)/$(basename "$1")
+P=${2:?usage: prof.sh [filter]}
+FILTER=${3:-}
+FG=${FLAMEGRAPH_DIR:-$HOME/FlameGraph}
+PIN=${PIN:-taskset -c 2}
+mkdir -p "$P"
+
+# Strip template arguments and parameter lists from folded frames.
+strip_names() {
+ python3 -I -c '
+import re,sys
+def short(f):
+ for _ in range(10):
+ g=re.sub(r"<[^<>]*>","",f)
+ if g==f: break
+ f=g
+ f=re.sub(r"\([^()]*\)( const)?","",f)
+ return f
+for l in sys.stdin:
+ l=l.rstrip("\n"); s,_,c=l.rpartition(" ")
+ print(";".join(short(x) for x in s.split(";"))+" "+c)'
+}
+
+one() { # op shape row
+ op=$1
+ shape=$2
+ row=$3
+ name=${op}_${shape}_${row}
+ # shellcheck disable=SC2086
+ perf record -q -e cycles:u -F 2000 --call-graph lbr -o "$P/$name.data" -- $PIN "$BIN" "$op" --rows "$row" \
+ --rounds 1 --round-ms 4000 --seed "${SEED:-1}" "$shape" >"$P/$name.out" 2>&1
+ perf script --inline -i "$P/$name.data" 2>/dev/null | "$FG/stackcollapse-perf.pl" >"$P/$name.raw.folded"
+ strip_names <"$P/$name.raw.folded" >"$P/$name.folded"
+ who=big_int
+ case $row in gmpz) who="GMP mpz" ;; inplace) who="big_int in-place" ;; esac
+ "$FG/flamegraph.pl" --minwidth "${MINW:-0.5}" --width 1200 --title "$op $shape $who${LABEL:+ ($LABEL)}" \
+ "$P/$name.folded" >"$P/$name.svg" 2>/dev/null
+ awk -v n="$name" '{c=$NF; t+=c; if ($0 !~ /(^|;)main(;| )/) m+=c} END {printf "%s samples=%d no-main=%.1f%%\n", n, t, 100*m/t}' \
+ "$P/$name.folded"
+}
+
+LIST="add:16x16:auto add:16x16:gmpz add:1000x1000:auto add:1000x1000:gmpz add:1000x1000:inplace
+mul:64x64:auto mul:64x64:gmpz mul:1024x1024:auto mul:1024x1024:gmpz mul:16384x16384:auto mul:16384x16384:gmpz
+mul:262144x262144:auto mul:262144x262144:gmpz divrem:256x128:auto divrem:256x128:gmpz divrem:4096x2048:auto
+divrem:4096x2048:gmpz divrem:131072x65536:auto divrem:131072x65536:gmpz tochars:10000x10:auto tochars:10000x10:gmpz
+fromchars:10000x10:auto fromchars:10000x10:gmpz gcd:1024x1024:auto gcd:1024x1024:gmpz"
+for e in $LIST; do
+ IFS=: read -r op shape row <<<"$e"
+ case "$op:$shape:$row" in *"$FILTER"*) one "$op" "$shape" "$row" ;; esac
+done
diff --git a/tests/beman/big_int/perf/tools/selftest.sh b/tests/beman/big_int/perf/tools/selftest.sh
new file mode 100755
index 00000000..905b5cd5
--- /dev/null
+++ b/tests/beman/big_int/perf/tools/selftest.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+# SPDX-License-Identifier: BSL-1.0
+# selftest.sh : run every op with every applicable row at small shapes (1 round, 2 ms).
+# The harness aborts on any correctness mismatch, so exit status 0 means all rows agree. Timings are not meaningful.
+set -u
+B=${1:?usage: selftest.sh