Performance
Benchmarks
Per-call timing for all measured functions across 5 platforms. All figures from the Phase 1 and Phase 2 validation runs on real hardware.
Phase 1 — Core functions
linalg, stats, roots, integrate, differentiate, interpolate, poly, ode.
| Function | Params | M1 Pro | M4 Pro | x86 i7 | ESP32-S3 | Win MSVC |
|---|---|---|---|---|---|---|
| numx_vec_dot | n=4 | 2 ns | — | — | 5,456 ns‡ | 91 ns‡ |
| numx_vec_norm | L2, n=2 | 7 ns | — | 166 ns‡ | 11,970 ns‡ | 166 ns‡ |
| numx_vec_cross3 | — | 1 ns | — | — | 332 ns | 3 ns |
| numx_mat_mul | 2×2 | 6 ns | — | — | 11,262 ns‡ | 98 ns‡ |
| numx_mat_det | 4×4 | 5 ns | — | — | 5,236 ns | 74 ns |
| numx_mat_transpose | 8×8 | — | — | — | 7,351 ns | 79 ns |
| numx_lu_decompose | 4×4 | 83 ns | 142 ns§ | 29 ns | 10,466 ns | 110 ns |
| numx_lu_solve | 4×4, pre-factored | — | § | 11 ns | 4,418 ns | 55 ns |
| numx_stats_mean | n=8 | 2 ns | 2 ns | 37 ns† | 6,194 ns† | 197 ns† |
| numx_stats_variance | pop, n=8 | 8 ns | 11 ns | 395 ns† | 69,560 ns† | 1,054 ns† |
| numx_stats_median | n=8 | 37 ns | 67 ns | 6,625 ns† | 1.11 ms† | 18,588 ns† |
| numx_stats_percentile | p50, n=8 | — | 32 ns | 3,297 ns† | — | 9,390 ns† |
| numx_root_bisect | x²-2, tol=1e-6 | 108 ns | 65 ns | 100 ns | 11,819 ns | 121 ns |
| numx_root_newton | x²-2, tol=1e-6 | 9 ns | 10 ns | 10 ns | 5,039 ns | 49 ns |
| numx_root_brent | x²-2, tol=1e-6 | 70 ns | 61 ns | 103 ns | 19,858 ns | 125 ns |
| numx_integrate_trap | n=100 | 111 ns | 117 ns | 139 ns | 24,487 ns | — |
| numx_integrate_simpson | n=100 | 102 ns | 104 ns | 183 ns | 28,868 ns | — |
| numx_integrate_gauss | npts=2 | 4 ns | 2 ns | 4 ns | 1,066 ns | — |
| numx_diff_forward | — | 3 ns | 2 ns | 3 ns | 864 ns | 8 ns |
| numx_diff_central | — | 3 ns | 2 ns | 2 ns | 939 ns | 7 ns |
| numx_diff_richardson | — | 4 ns | 4 ns | 4 ns | 2,141 ns | 11 ns |
| numx_interp_linear | n=5 | 2 ns | 2 ns | 3 ns | 1,381 ns | — |
| numx_interp_spline_eval | n=5, pre-built | 3 ns | 2 ns | 3 ns | 2,479 ns | — |
| numx_interp_chebyshev | n=8 | 118 ns | 137 ns | 315 ns | 126,311 ns | — |
| numx_poly_eval | degree=3 | 3 ns | 1 ns | 1 ns | 407 ns | — |
| numx_poly_roots | degree=3 | 78 ns | 41 ns | 104 ns | 20,350 ns | — |
| numx_ode_rk4 | n=1, 100 steps | 3,363 ns | — | — | 173,201 ns | 1,745 ns |
| numx_ode_rk45 | tol=1e-4 | 470 ns | — | — | 39,634 ns | 339 ns |
† x86 / ESP32 / Win stats benchmarks used n=128; M1 / M4 Pro used n=8. x86 / ESP32 / Win vec_dot and vec_norm used n=64.
‡ ESP32 and Win measured mat_mul and mat_det at 4×4; M1 Pro at 2×2. vec_dot / vec_norm at larger n on non-Apple platforms.
§ M4 Pro lu_decompose time includes lu_solve in a single combined measurement; lu_solve row shows § to indicate this.
Phase 2 — Advanced modules
autodiff, fft, signal, sketch (rsvd), compressed_sensing.
| Function | Params | M1 Pro | M4 Pro | x86 i7 | ESP32-S3 | Win MSVC |
|---|---|---|---|---|---|---|
| numx_dual (fwd) | mul-chain depth=10 | 8 ns | —¶ | 20 ns | — | — |
| numx_tape (rev) | grad x²+y² | 667 ns | 1,115 ns | 1,083 ns | — | — |
| numx_tape (rev) | grad x²+y²+z² | 690 ns | — | 1,105 ns | — | — |
| numx_fft_f32 | N=64 | 2,547 ns | 2,767 ns | 3,582 ns | 2.58 ms⚠ | 12,420 ns |
| numx_fft_f32 | N=256 | 13,507 ns | 10,429 ns | 22,578 ns | 13.8 ms⚠ | 70,745 ns |
| numx_fft_f32 | N=512 | 30,423 ns | 23,218 ns | 54,699 ns | 31.1 ms⚠ | 161,765 ns |
| numx_ifft_f32 | N=256 | 13,470 ns | 10,134 ns | 22,475 ns | 13.8 ms⚠ | 71,473 ns |
| numx_fft_q15 | N=256 | 12,981 ns | 10,326 ns | 23,029 ns | 14.3 ms⚠ | 69,217 ns |
| numx_fft_magnitude | N=256 | 19,542 ns | 16,349 ns | 16,336 ns | 3,684 µs⚠ | 29,800 ns |
| numx_signal_window_hann | n=512 | 3,839 ns | 3,319 ns | 9,058 ns | 4,531 ns* | — |
| numx_signal_convolve | xn=256, hn=32 | 2,310 ns | 1,343 ns | 3,241 ns | 668 ns* | — |
| numx_signal_fir | xn=256, ntaps=32 | 4,892 ns | 3,042 ns | 3,052 ns | 1,344 ns* | — |
| numx_signal_iir_biquad | n=256 | 770 ns | 334 ns | 508 ns | 521 ns* | — |
| numx_signal_ema | n=256, alpha=0.1 | 540 ns | 275 ns | 318 ns | 303 ns* | — |
| numx_sketch_rsvd | 16×16, rank=4 | 375,420 ns | 242,060 ns | 332,972 ns | — | 466,460 ns |
| numx_sketch_rsvd | 32×32, rank=8 | 953,090 ns | 606,790 ns | 805,758 ns | — | — |
| numx_sketch_rsvd | 64×64, rank=8 | 1,101,590 ns | 667,210 ns | 931,322 ns | — | — |
| numx_cs_spectral_norm | 16×32, iter=32 | 21,910 ns | 14,030 ns | 20,327 ns | 62,570 ns | — |
| numx_cs_omp | 16×32, k=4 | 1,170 ns | 860 ns | 1,363 ns | 3,670 ns | — |
| numx_cs_ista | 16×32, λ=0.1, iter=500 | 167,650 ns | 111,440 ns | 182,629 ns | 161,090 ns∥ | — |
¶ M4 Pro autodiff ran a "dual fwd x2" micro-benchmark; "mul-chain depth=10" result not available for that platform.
⚠ ESP32 FFT timings are ~1000× above expectation due to a software-float bug in the trig kernel (priv_cos/priv_sin calls __divsf3 instead of the hardware FPU). This is a known implementation bug, not a platform limitation.
* ESP32 signal benchmarks used smaller arrays (n=64–128) than the 256-element benchmark on other platforms.
∥ ESP32 ISTA measured at iter=100; other platforms at iter=500.
Methodology
Timer
clock_gettime(CLOCK_MONOTONIC) on Linux/macOS. QueryPerformanceCounter on Windows. esp_timer_get_time() on ESP32 (1 µs resolution). Per-call time = total wall time / N iterations.
Iteration counts
N ranges from 100 (sketch_rsvd, rsvd 64×64) to 100,000 (vec_dot, diff_*). Fast functions use higher N to amortise timer overhead. Slow functions (ODE, sketch) use lower N to keep total run time under a minute.
Build flags
float32 throughout (default build, NUMX_USE_DOUBLE not set). -O2 on all platforms. Apple clang 17 on M1 Pro, Apple clang 21 on M4 Pro, gcc 11.4 on x86-64, MSVC 14.51 on Windows, xtensa-esp32s3-elf-gcc on ESP32-S3.
Isolation
Apple results from a clean-rebooted system with no other user sessions. x86-64 results from a shared Ubuntu lab machine (±10-15% variance). ESP32-S3 at 240 MHz (actual clock; task-watchdog may fire on long loops — results remain valid).