Every Phoenix request touches the same hot paths: template rendering, HTML escaping, JSON encoding, route matching, CSRF validation, cookie parsing. These are compute-bound operations where BEAM's strengths (concurrency, fault tolerance, I/O) don't help — but WASM's compiled execution dominates.
The headline numbers:
| Phoenix Hot Path | BEAM | WASM | Speedup | Why |
|---|---|---|---|---|
| Template (150 vars) | 880 μs | 9.4 μs | 94x | Indexed slots vs String.replace chain |
| Template (60 vars) | 316 μs | 5.6 μs | 56x | Pre-compiled, no key hashing |
| Batch Dispatch (1K reqs) | 3,110 μs | 64 μs | 49x | Zero per-request boundary crossing |
| Batch Dispatch (1M reqs) | 3,110,000 μs | 64,000 μs | 49x | Linear scaling, amortized NIF call |
| JSON Encode (12 fields) | 50.6 μs | 1.2 μs | 42x | Single-pass compiled serializer |
| HTML Hash (change detect) | 30.7 μs | 0.8 μs | 38x | FNV-1a tight loop, no allocation |
| JSON Encode (4 fields) | 20.2 μs | 0.71 μs | 28x | No map traversal or intermediate lists |
| Content Negotiation (5 types) | 7.25 μs | 0.50 μs | 14.4x | Single-pass parse+sort+match |
| Batch Routing (1K reqs) | 1,950 μs | 147 μs | 13.3x | Compiled trie, amortized calls |
| Batch Routing (1M reqs) | 1,950,000 μs | 147,000 μs | 13.3x | Compiled trie, fully amortized |
| WS Upgrade Detection | 6.48 μs | 0.52 μs | 12.4x | Compiled header parser |
| Form (17 fields + selects) | 174 μs | 26.3 μs | 6.6x | SIMD escaping + pre-alloc buffer |
| HTML Escape (4.9KB page) | 101 μs | 17.3 μs | 5.8x | SIMD128 vectorized scanning |
| Request Dispatch | 2.68 μs | 0.46 μs | 5.8x | Compiled trie + action lookup |
| CSRF Token Generation | 5.53 μs | 0.81 μs | 6.8x | WASM PRNG + compiled signing |
| Cookie Signing | 3.81 μs | 0.68 μs | 5.6x | Compiled HMAC, no NIF crossing |
| JSON Response (20 fields) | 60.1 μs | 5.8 μs | 10.3x | Compiled encode + frame in one pass |
| Batch JSON Responses (1K) | 35,800 μs | 3,830 μs | 9.3x | Amortized encode + frame pipeline |
| Batch JSON Responses (1M) | 35,800,000 μs | 3,830,000 μs | 9.3x | Amortized encode + frame pipeline |
| Query String (10 params) | 10.15 μs | 1.27 μs | 8.0x | Compiled parser + URL decode |
| Redirect (302) | 3.42 μs | 0.50 μs | 6.9x | Compiled escape + template + frame |
| Paginated API (10 items) | 213 μs | 39.8 μs | 5.4x | Compiled JSON array + metadata |
| JSON Array (20 items) | 396 μs | 67.4 μs | 5.9x | Single-pass array serializer |
| Session Decode (8 fields) | 6.08 μs | 1.27 μs | 4.8x | Compiled hex decoder |
| Paginated API (25 items) | 736 μs | 151 μs | 4.9x | Compiled encode + pagination meta |
| Error Response (404) | 2.34 μs | 0.53 μs | 4.4x | Compiled JSON + status + frame |
| LiveView Full Cycle | 94.6 μs | 24.0 μs | 3.9x | Compiled diff→hash→render |
Your existing Phoenix code. Same semantics. Faster execution.
Benchmarked on Elixir 1.14.0 / OTP 25, single vCPU. All results reproducible — see Performance Deep Dive for methodology and 75+ benchmarks.
Note: 1K and 1M batch numbers are extrapolated from per-request costs measured at smaller batch sizes, assuming linear scaling. Actual benchmarks at these scales may show slightly better WASM speedups due to further amortization of NIF call overhead.
The key insight: not everything should be WASM. Use WASM for compute-bound hot paths, BEAM for what it's built for.
WASM wins (compute-bound) BEAM wins (I/O-bound)
────────────────────────── ──────────────────────
✅ Template rendering (8-94x) ✅ Database queries (Ecto)
✅ JSON encoding (28-42x) ✅ Network I/O (HTTP clients)
✅ API pagination (4-6x, arrays) ✅ Ecto queries / changesets
✅ HTML escaping (5-7x, SIMD) ✅ GenServer orchestration
✅ Query string parsing (7-10x) ✅ PubSub / message passing
✅ Route matching (3-13x, trie) ✅ Process supervision
✅ CSRF/HMAC/signing (5-7x) ✅ O(1) binary append
✅ Cookie & session ops (2-5x) ✅ Pattern matching (trivial)
✅ LiveView diffs (2-4x) ✅ Base.encode16 (native NIF)
✅ Form building (5-8x) ✅ URL decode (no encoding, trivial)
✅ Content negotiation (3-14x) ✅ WS frame encode (binary)
✅ Response building (4-10x) ✅ Trivial validations (<200ns)
✅ Request dispatch (6-49x batch 1K-1M) ✅ Rate limiting (simple map ops)
✅ HTTP framing (pre-built body)
Firebird makes this seamless — annotate compute-hot functions with @wasm true and they compile to WebAssembly automatically. BEAM handles supervision, I/O, and message passing. WASM handles the math.