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.

ARM64 M1 Pro
Apple clang 17, -O2
complete
ARM64 M4 Pro
Apple clang 21, -O2
complete
x86-64 i7-13700H
gcc 11.4, Ubuntu 22.04, -O2
complete
ESP32-S3 LX7
ESP-IDF v5.5.2, 240 MHz
complete
Windows x64 MSVC
MSVC 14.51 / VS 2026 Build Tools
complete

Phase 1 — Core functions

linalg, stats, roots, integrate, differentiate, interpolate, poly, ode.

FunctionParamsM1 ProM4 Prox86 i7ESP32-S3Win MSVC
numx_vec_dotn=42 ns5,456 ns‡91 ns‡
numx_vec_normL2, n=27 ns166 ns‡11,970 ns‡166 ns‡
numx_vec_cross31 ns332 ns3 ns
numx_mat_mul2×26 ns11,262 ns‡98 ns‡
numx_mat_det4×45 ns5,236 ns74 ns
numx_mat_transpose8×87,351 ns79 ns
numx_lu_decompose4×483 ns142 ns§29 ns10,466 ns110 ns
numx_lu_solve4×4, pre-factored§11 ns4,418 ns55 ns
numx_stats_meann=82 ns2 ns37 ns†6,194 ns†197 ns†
numx_stats_variancepop, n=88 ns11 ns395 ns†69,560 ns†1,054 ns†
numx_stats_mediann=837 ns67 ns6,625 ns†1.11 ms†18,588 ns†
numx_stats_percentilep50, n=832 ns3,297 ns†9,390 ns†
numx_root_bisectx²-2, tol=1e-6108 ns65 ns100 ns11,819 ns121 ns
numx_root_newtonx²-2, tol=1e-69 ns10 ns10 ns5,039 ns49 ns
numx_root_brentx²-2, tol=1e-670 ns61 ns103 ns19,858 ns125 ns
numx_integrate_trapn=100111 ns117 ns139 ns24,487 ns
numx_integrate_simpsonn=100102 ns104 ns183 ns28,868 ns
numx_integrate_gaussnpts=24 ns2 ns4 ns1,066 ns
numx_diff_forward3 ns2 ns3 ns864 ns8 ns
numx_diff_central3 ns2 ns2 ns939 ns7 ns
numx_diff_richardson4 ns4 ns4 ns2,141 ns11 ns
numx_interp_linearn=52 ns2 ns3 ns1,381 ns
numx_interp_spline_evaln=5, pre-built3 ns2 ns3 ns2,479 ns
numx_interp_chebyshevn=8118 ns137 ns315 ns126,311 ns
numx_poly_evaldegree=33 ns1 ns1 ns407 ns
numx_poly_rootsdegree=378 ns41 ns104 ns20,350 ns
numx_ode_rk4n=1, 100 steps3,363 ns173,201 ns1,745 ns
numx_ode_rk45tol=1e-4470 ns39,634 ns339 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.

FunctionParamsM1 ProM4 Prox86 i7ESP32-S3Win MSVC
numx_dual (fwd)mul-chain depth=108 ns—¶20 ns
numx_tape (rev)grad x²+y²667 ns1,115 ns1,083 ns
numx_tape (rev)grad x²+y²+z²690 ns1,105 ns
numx_fft_f32N=642,547 ns2,767 ns3,582 ns2.58 ms⚠12,420 ns
numx_fft_f32N=25613,507 ns10,429 ns22,578 ns13.8 ms⚠70,745 ns
numx_fft_f32N=51230,423 ns23,218 ns54,699 ns31.1 ms⚠161,765 ns
numx_ifft_f32N=25613,470 ns10,134 ns22,475 ns13.8 ms⚠71,473 ns
numx_fft_q15N=25612,981 ns10,326 ns23,029 ns14.3 ms⚠69,217 ns
numx_fft_magnitudeN=25619,542 ns16,349 ns16,336 ns3,684 µs⚠29,800 ns
numx_signal_window_hannn=5123,839 ns3,319 ns9,058 ns4,531 ns*
numx_signal_convolvexn=256, hn=322,310 ns1,343 ns3,241 ns668 ns*
numx_signal_firxn=256, ntaps=324,892 ns3,042 ns3,052 ns1,344 ns*
numx_signal_iir_biquadn=256770 ns334 ns508 ns521 ns*
numx_signal_eman=256, alpha=0.1540 ns275 ns318 ns303 ns*
numx_sketch_rsvd16×16, rank=4375,420 ns242,060 ns332,972 ns466,460 ns
numx_sketch_rsvd32×32, rank=8953,090 ns606,790 ns805,758 ns
numx_sketch_rsvd64×64, rank=81,101,590 ns667,210 ns931,322 ns
numx_cs_spectral_norm16×32, iter=3221,910 ns14,030 ns20,327 ns62,570 ns
numx_cs_omp16×32, k=41,170 ns860 ns1,363 ns3,670 ns
numx_cs_ista16×32, λ=0.1, iter=500167,650 ns111,440 ns182,629 ns161,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).