Benchmarks
Apsi, measured
Each category compares Apsi with the tool people use for that job today, on the same machine, with the command to reproduce it. Pick a category.
Apsi 2.4.4 against 2.0.0
Reproduce:python3 benchmarks/versions/compare.py --old ../apsi-2.0.0 --new . --webThe same programs on both versions, on the same machine, with the same results. On a GitHub Actions runner with the native compiler, 2.4.4 is 3.84× faster on the geometric mean of 12 measurements; on a small 2-core VM without the native compiler, 7.31×. The gains are where 2.0.0 was weakest: array code written as loops, objects, function calls and the web server. The CPU tab compares the heavy @native programs of both versions.
| Program | Apsi 2.0.0 | Apsi 2.4.4 | Change | Change on a 2-core VM ¹ |
|---|---|---|---|---|
| matmul 300, loops (compiled engine) | 38,157 ms | 5.0 ms | 7,708× faster | 6,374× faster |
| 200k objects: create and sum | 3,758 ms | 434 ms | 8.65× faster | 9.26× faster |
| fib(25), recursive calls | 82.8 ms | 24.5 ms | 3.37× faster | 2.26× faster |
| web server, 64 connections | 5,124 req/s | 13,949 req/s | 2.72× more | 3.06× faster |
| matmul 512, @native loops | 24.7 ms | 9.6 ms | 2.56× faster | 300× faster |
| web server, 256 connections | 5,848 req/s | 14,052 req/s | 2.4× more | 3.18× faster |
| word count, 260k words | 320 ms | 205 ms | 1.56× faster | 1.41× faster |
| start-up: apsi run hello.apsi | 231 ms | 173 ms | 1.34× faster | 6.69× faster |
| 200k f-strings and join | 236 ms | 192 ms | 1.23× faster | 1.26× faster |
| apsi check, 43 example programs (1,594 lines), empty cache | 9,865 ms | 9,526 ms | 1.04× faster | 4.90× faster |
| integer loop, 3M iterations | 171 ms | 168 ms | 1.02× faster | same |
| harmonic sum 50M, @native | 70.1 ms | 70.1 ms | 1.0× faster | same |
¹ A 2-core cloud VM (Intel Xeon @ 2.30 GHz) without llvmlite: both versions ran the @native functions without the native compiler there, and 2.0.0 started much more slowly. Main columns: AMD EPYC 7763, GitHub Actions runner (4 vCPU), Linux, Python 3.13, llvmlite installed; best of 2 runs.
Every commit of the speed work, on one machine
Reproduce:python3 benchmarks/commits/measure.py --commits 1187386 7810294 040e971 --rounds 3Numbers from different GitHub runs cannot be compared: GitHub picks the processor, and the same C program takes 14 ms on one runner model and 44 ms on another. So every commit was measured on one runner, in rotating rounds, against the ones before it. From the start of the work (1187386) to the release (040e971): n-body 4.0×, the ODE 1.5×, k-means 1.27×, compiling 1.21× faster, and no row slower.
| Measurement | 1187386 (start) | 7810294 | 040e971 (release) |
|---|---|---|---|
| heavy: nbody-dense 2000x10 | 184.4 (1.00×) | 127.7 (1.44×) | 46.0 (4.01×) |
| heavy: jacobi-2d 1024x200 | 156.4 (1.00×) | 156.9 (1.00×) | 152.5 (1.03×) |
| heavy: matmul 1024 | 189.3 (1.00×) | 182.6 (1.04×) | 179.0 (1.06×) |
| heavy: lu-pivot 800 | 41.4 (1.00×) | 41.2 (1.00×) | 41.4 (1.00×) |
| heavy: ising 512x50 | 81.5 (1.00×) | 81.6 (1.00×) | 81.5 (1.00×) |
| heavy: levenshtein 10k | 194.8 (1.00×) | 195.1 (1.00×) | 195.1 (1.00×) |
| heavy: ode-rk4 20M | 533.4 (1.00×) | 355.0 (1.50×) | 355.0 (1.50×) |
| heavy: kmeans 200k k16 x20 | 65.0 (1.00×) | 51.3 (1.27×) | 51.3 (1.27×) |
| programs: matmul 300, loops (compiled engine) | 38,245.6 (1.00×) | 38,307.7 (1.00×) | 38,642.7 (0.99×) |
| programs: matmul 512, @native loops | 25.9 (1.00×) | 24.8 (1.04×) | 26.0 (0.99×) |
| programs: harmonic sum 50M, @native | 70.0 (1.00×) | 70.0 (1.00×) | 70.1 (1.00×) |
| programs: fib(25), recursive calls | 24.8 (1.00×) | 24.6 (1.01×) | 24.1 (1.03×) |
| programs: 200k objects: create and sum | 431.8 (1.00×) | 434.8 (0.99×) | 435.1 (0.99×) |
| programs: word count, 260k words | 201.7 (1.00×) | 201.2 (1.00×) | 203.7 (0.99×) |
| programs: 200k f-strings and join | 193.9 (1.00×) | 190.4 (1.02×) | 188.2 (1.03×) |
| programs: integer loop, 3M iterations | 168.6 (1.00×) | 172.3 (0.98×) | 171.7 (0.98×) |
| start-up: apsi run hello.apsi | 173.0 (1.00×) | 173.3 (1.00×) | 172.3 (1.00×) |
| import apsi | 133.9 (1.00×) | 133.6 (1.00×) | 133.4 (1.00×) |
| cold heavy.apsi: compile + run, whole process | 7,290.7 (1.00×) | 6,617.3 (1.10×) | 6,031.9 (1.21×) |
| apsi check, 43 examples, empty cache | 9,900.8 (1.00×) | 9,788.9 (1.01×) | 9,628.6 (1.03×) |
AMD EPYC 7763, GitHub Actions runner (4 threads), Linux, Python 3.13, llvmlite installed. Times in ms, best of 3 rounds; in brackets: time of the first commit / time of this commit.
Python, converted
Reproduce:benchmarks/python/cmp.sh · tests/test_stdlib_convert.pyConverted programs print byte-identical output. Object-heavy code runs 6 % faster than CPython; string and pure arithmetic code runs at about the same speed (within 4 %; use @native for machine code). Best of 5 runs each.
| Program | CPython 3.13 | Converted, apsi run |
|---|---|---|
| objects: trees, dataclasses, dicts, Counter | 0.402 s | 0.378 s |
| strings: regex, split, JSON, sets | 0.363 s | 0.377 s |
| n-body, 200,000 steps | 1.388 s | 1.407 s |
| Standard-library modules converted: identical output (tests/test_stdlib_convert.py) | yes | yes |
Matrix multiplication
Reproduce:benchmarks/matmul/mm.apsi · mm.c · mm.py · tests/test_idioms.pyThree nested loops, written the obvious way, are recognised as a matrix product and run on the tuned BLAS kernel: 4–11× faster than the same loops in C with -O3, about 2,400× faster than Python loops. The loops stay as the fallback whenever the fast path would not give the loops' result. Best of 3 runs; BLAS uses all 4 vCPUs, the C loops one.
| Size (float64) | C loops, gcc -O3 -march=native | NumPy a @ b | Apsi loops (ikj) | Apsi loops (dot form) | Apsi a @ b |
|---|---|---|---|---|---|
| 256 × 256 | 2.28 ms · 14.7 GFLOP/s | 0.61 ms | 0.55 ms · 61.0 GFLOP/s | 0.45 ms | 0.46 ms |
| 512 × 512 | 18.85 ms · 14.2 GFLOP/s | 3.20 ms | 3.40 ms · 79.0 GFLOP/s | 3.20 ms | 3.17 ms |
| 1024 × 1024 | 155.2 ms · 13.8 GFLOP/s | 23.98 ms | 24.27 ms · 88.5 GFLOP/s | 23.77 ms | 23.49 ms |
| 2048 × 2048 | 2,111 ms · 8.1 GFLOP/s | 190.7 ms | 192.4 ms · 89.3 GFLOP/s | 189.2 ms | 190.2 ms |
| Python loops, 128 × 128 | — | — | 114.0 ms in CPython (0.037 GFLOP/s) |
Tables against pandas
Reproduce:benchmarks/dataframe/run.pyuse dataframe on 2 million rows against pandas, results checked equal; best of 3 runs. Filters, group-bys and joins are several times faster; sorting is faster; reading CSV is about as fast as pandas.
| Operation | pandas 2 | Apsi 2.4.4 | Speed-up |
|---|---|---|---|
| Filter with two conditions | 98.8 ms | 4.6 ms | 21.5× |
| Group by 50,000 string keys (sum, mean, count) | 202.2 ms | 35.6 ms | 5.7× |
| Group by two keys (std, max) | 170.1 ms | 39.0 ms | 4.4× |
| Left join 2M × 50k on a string key | 168.9 ms | 45.1 ms | 3.7× |
| Sort by a float column | 168.4 ms | 105.1 ms | 1.6× |
| Arithmetic on two columns | 2.8 ms | 2.7 ms | same |
| Read a 1M-row CSV | 0.18 s | 0.20 s | 1.1× slower |
| Memory for the table | 262 MB | 65 MB | 4× less |
HTTP server (synthetic)
Reproduce:benchmarks/web/Synthetic benchmark: one route returning a fixed 25-byte JSON object, keep-alive, loopback, the Go load generator on the same 4-vCPU machine (it takes CPU time from the server too). It measures the server's own cost per request, not a real application (no database, no request parsing, no TLS). With processes="auto" Apsi answers 1.8× the requests of a single Node.js 22 process; a single Apsi process answers about two thirds of Node's. Three rounds of 5 seconds each; the range is shown.
| Setup | 64 connections | 256 connections |
|---|---|---|
| Apsi, 1 process (asyncio loop) | 14,008–14,978 req/s | 13,671–14,830 req/s |
| Apsi, 1 process, uvloop | 15,493–16,863 req/s | 14,958–15,392 req/s |
| Apsi, processes="auto" (4 processes) | 41,718–42,085 req/s | 44,042–44,269 req/s |
| Node.js 22, bare http module, 1 process | 19,872–22,198 req/s | 22,862–25,155 req/s |
| Fast route while 64 connections wait on a blocking 10 ms route (Apsi, 1 process) | 9,888 req/s, p99 7.9 ms | — |
| 1,000 connections at once (Apsi, 1 process) | — | 15,454 req/s, 0 errors |
Each Apsi process has its own memory: keep shared state in a database or memory.store(). Run: benchmarks/web/ (pip install uvloop for the uvloop rows).
Real-time video, CPU only
Reproduce:benchmarks/rt/Gray conversion + motion detection on every frame, on the CPU only (one core of a 4-vCPU runner); a capped run holds its target while the frame work fits in the frame time — up to 2560×1440 at 60 fps here; 4K reaches 20 of 30 fps on this machine. A 2000 Hz control loop ran 6,000 of 6,000 ticks with p99 lateness 0.3–0.4 µs (worst single tick 26–48 µs).
| Size | Uncapped fps | Capped fps (target) | Latency p50 |
|---|---|---|---|
| 640×480 | 708 | 240.0 (240) | 1.82 ms |
| 800×600 | 506 | 240.0 (240) | 1.80 ms |
| 1280×720 | 290 | 120.0 (120) | 3.42 ms |
| 1366×768 | 251 | 120.0 (120) | 3.86 ms |
| 1600×900 | 169 | 90.0 (90) | 5.16 ms |
| 1920×1080 | 106 | 90.0 (90) | 7.61 ms |
| 2560×1440 | 50 | 57.1 (60) | 14.65 ms |
| 3840×2160 | 20 | 20.0 (30) | 36.46 ms |
Models that learn
Reproduce:benchmarks/ai/learn.apsiTrained from scratch on the CPU with use layers; held-out accuracy before → after training (the same in all 3 runs; time: best of 3).
| Task | Before | After | Time |
|---|---|---|---|
| 3-class spiral, MLP 64-64 | 30.8 % | 100 % | 1.1 s |
| Remember the first of 20 steps, LSTM 32 | 53.5 % | 100 % | 17.5 s |
| Character transformer (2 layers), loss | 1.693 | 0.019 | 10.3 s |
Fewer AI tokens, nothing lost
Reproduce:docs/TOKENS.mduse tokens: every transform is exactly reversible. Token counts estimated.
| Input | Before | After | Saved |
|---|---|---|---|
| 200 orders as pretty JSON → pack() | 9,802 | 3,625 | 63 % |
| 200 orders as minified JSON → pack() | 8,401 | 3,625 | 57 % |
| 500-line log → dedupe() | 5,689 | 2,339 | 59 % |
| Edited 6,000-token document → diff() | 5,951 | 19 | 99.7 % |
WebSocket and databases
Reproduce:benchmarks/websocket/ · benchmarks/db/Built-in clients and servers, no drivers to install. PostgreSQL 16 and Redis 7 in Docker on the same machine; best of 3 runs.
| Measurement | Result |
|---|---|
| WebSocket round trip (Node.js 22 client) | 225 µs |
| WebSocket burst, in order | 50,591 messages/s |
| WebSocket: 1,000 concurrent connections | all answered |
| PostgreSQL 16: 2,000 point queries | 397 ms (198 µs each) |
| PostgreSQL 16: insert 20,000 rows | 218 ms |
| PostgreSQL 16: read 20,000 rows | 70 ms |
| Redis 7: 10,000 pipelined pushes | 45 ms |
| Redis 7: 2,000 SET round trips | 257 ms (128 µs each) |
CPU programs against compiled languages
Reproduce:python3 benchmarks/heavy/run.pyResults table · Apsi 2.4.4
| Program | Apsi | C | C++ | Rust | Go |
|---|---|---|---|---|---|
| nbody-dense 2000x10All-pairs gravity for 2 000 bodies, 10 steps: 40 million interactions with a square root and a division each. | 47 ms | 44 ms | 44 ms | 182 ms | 317 ms |
| jacobi-2d 1024x200Heat equation on a 1024 × 1024 grid, 200 Jacobi sweeps of a 5-point stencil (memory-bandwidth bound). | 151 ms | 167 ms | 157 ms | 167 ms | 701 ms |
| matmul 1024Dense 1024 × 1024 matrix product, i-k-j loop order (2.1 GFLOP). | 178 ms | 200 ms | 165 ms | 182 ms | 479 ms |
| lu-pivot 800LU factorisation with partial pivoting of an 800 × 800 matrix, returning log |det A|. | 40 ms | 40 ms | 40 ms | 41 ms | 117 ms |
| ising 512x50Metropolis Monte Carlo of a 512 × 512 Ising model, 50 sweeps: 13 million random, branch-heavy updates. | 82 ms | 91 ms | 90 ms | 84 ms | 146 ms |
| levenshtein 10kEdit distance between two 10 000-symbol sequences: 100 million dynamic-programming cells. | 195 ms | 809 ms | 306 ms | 269 ms | 1737 ms |
| ode-rk4 20MVan der Pol oscillator, classic Runge–Kutta 4, 20 million steps: a long chain of dependent operations. | 355 ms | 289 ms | 289 ms | 355 ms | 355 ms |
| kmeans 200k k16 x20k-means clustering of 200 000 points into 16 clusters, 20 Lloyd iterations. | 51 ms | 61 ms | 59 ms | 64 ms | 213 ms |
| Sum of all programs | 1098 ms | 1700 ms | 1150 ms | 1343 ms | 4065 ms |
| Time relative to C (geometric mean) | 0.81× | 1.00× | 0.85× | 1.05× | 2.74× |
Compare versions
On the same programs, Apsi 2.4.4 is 1.29× faster than Apsi 2.0.0 (geometric mean).
| Program | Apsi 2.4.4 | Apsi 2.0.0 | Change | Time, 2.4.4 vs 2.0.0 |
|---|---|---|---|---|
| nbody-dense 2000x10 | 47 ms | 183 ms | 3.93× faster | |
| jacobi-2d 1024x200 | 151 ms | 151 ms | 1.01× slower | |
| matmul 1024 | 178 ms | 179 ms | 1.01× faster | |
| lu-pivot 800 | 40 ms | 40 ms | 1.01× faster | |
| ising 512x50 | 82 ms | 82 ms | 1.00× faster | |
| levenshtein 10k | 195 ms | 194 ms | 1.01× slower | |
| ode-rk4 20M | 355 ms | 533 ms | 1.50× faster | |
| kmeans 200k k16 x20 | 51 ms | 65 ms | 1.27× faster |
Bars use a log scale so very different times stay readable. Both versions measured on the same machine in one session. Apsi 2.0.0: apsi run (@native, strict IEEE).
How it was measured
- Processor: AMD EPYC 7763 64-Core Processor (GitHub Actions runner), one core (all programs are single-threaded).
- Operating system: Linux x86-64.
- Method: best of 5 rounds; Apsi 2.4.4 and 2.0.0 measured in turn on the same runner, 2026-11-10.
- Apsi: Apsi 2.4.4 —
apsi run (@native, strict IEEE) - C: gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 —
gcc -O3 -march=native - C++: g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 —
g++ -O3 -march=native - Rust: rustc 1.99.1 (58cf8f9c3 2026-10-13) —
rustc -C opt-level=3 -C target-cpu=native - Go: go version go1.24.13 linux/amd64 —
go build - Every program prints a checksum; results count only when all languages agree.
Run it yourself — one command
python3 benchmarks/heavy/run.py
Builds every installed language, runs all eight programs and shows a live table that fills in as each result arrives. Add --publish 2.4.4 and this page updates in real time while it runs. --apsi-root ../apsi-1.10.0 measures an older Apsi.
Results depend on the processor and compiler versions. Apsi keeps strict IEEE floating point (no fast-math reordering); its times include calling the compiled code from the Apsi runtime.
Machine for the 2.4.4 categories: AMD EPYC 7763, GitHub Actions runner (4 vCPU), Linux, Python 3.13, llvmlite installed; load generators and databases on the same machine; best of 3–5 rounds.