Bend 2 vs. C: Single-Core Parity, Multi-Core/GPU Scaling, and Why Quadtree Matrix Multiply Beats Naive C
How Bend 2 compiles to a single C11 file
(bend -o prog.c), how it compares to clang -O3
across all 16 Apple M4 Max benchmarks, and when to choose C vs. Bend
2.
Executive Summary: Bottom Line Up Front
Bend 2 does not replace C; it compiles directly to a single,
self-contained C file (bend prog.bend -o prog.c)
(bendlang/bend). That
emitted file serves simultaneously as a single-core CPU binary, a
16-core pthread runner, and an Apple Metal or NVIDIA CUDA GPU kernel (bendlang/bend).
- Choose C (
clang -O3/gcc -O3) for operating system kernels, device drivers, embedded microcontrollers, and algorithms dominated by in-place mutation over flat contiguous byte buffers. On flat hash tables (hashmap:0.622 sC vs.2.741 sBendSEQ-CPU) and contiguous byte-stream tokenizers (lexer:1.036 sC vs.2.144 sBendSEQ-CPU), single-core C beats single-core Bend by2.07xto4.41x(benchmark dataset). - Choose Bend 2 (
bend -o) when your algorithm recursively decomposes trees, spatial grids, graphs, or sorting networks and you want multi-core CPU (PAR-CPU) and GPU (PAR-GPU) scaling without writingpthread_create, atomics, or CUDA shaders. Across 14 of the 16 official Apple M4 Max benchmarks, single-core Bend (SEQ-CPU) runs within0.70xto1.50xof hand-written-O3C, actually beating C on quadtree matrix multiplication (2.064 svs.2.964 s). Switching onPAR-CPU(16 cores) andPAR-GPU(Metal) accelerates the exact same source file up to 107.6x faster than sequential C (0.063 svs.6.776 son Conway’s Game of Life) (benchmark dataset).
1. How
bend -o prog.c Works Under the Hood
Legacy functional runtimes (including Bend 1 / HVM2) suffered on
single-core CPUs because they represented every function call as a
heap-allocated interaction-net node (HigherOrderCO/HVM). Bend
2’s BendRT compiler (bend2/comp.ts)
eliminates graph-node interpretation in favor of four low-level C
codegen techniques (bendlang/bend):
- Zero C Call Stack (
musttailWorklist Machine): Bend functions do not push C stack frames (call/ret). Instead, every function compiles to a segment inside a flat worklist loop (WL_CASE(FID_...)) usingmusttailjumps with register-pinned state. - Inline 64-Bit Term Packing: Every Bend value is a
64-bit word (
[rfc(1) | tag(7) | aux(16) | loc(40)]). Machine scalars (U32,F32), natural numbers below 248, and unary constructors (Leaf{x}) are packed directly into the 64-bit word with zeromalloccalls and zero heap bytes (benchmark dataset). - Whole-Program Share & Borrow Inference: When a
+variable is only read across recursive calls, BendRT’s borrow inference passes raw 64-bit pointers without bumping reference counts. - Instant Affine Deallocation
(
heap_free): Because affine variables are consumed at most once, pattern matching (case Node{l, r}:) immediately returns its 2-word slot to a thread-local freelist (heap_free). Resident memory stays between 2.0 MB and 2.8 MB RSS on 12 of the 16 M4 Max benchmarks (benchmark dataset).
2.
Complete Apple M4 Max Benchmark Suite: Hand-Written C
(clang -O3) vs. Bend 2
The table below reports all 16 workloads from the pinned Apple M4 Max
benchmark suite (/data/bend-ecosystem.json), comparing
hand-written sequential C (c_sec) against Bend 2
SEQ-CPU, 16-core PAR-CPU, and Metal
PAR-GPU (benchmark
dataset):
| Workload | Category | C (clang -O3) |
Bend SEQ-CPU (RSS) |
SEQ / C Ratio | Bend PAR-CPU 16c |
Bend PAR-GPU Metal |
PAR-GPU vs. C |
|---|---|---|---|---|---|---|---|
| Tree Matrix Multiply | Quadtree Algebra | 2.964 s |
2.064 s
(2.8 MB) |
0.70x (Bend faster!) |
0.226 s |
0.231 s |
12.83x faster (source) |
| K-Means Clustering | Machine Learning | 2.062 s |
2.069 s
(23.5 MB) |
1.00x (Tie) |
0.268 s |
0.191 s |
10.80x faster (source) |
| Procedural Terrain | Simplex Noise | 2.198 s |
2.225 s
(2.1 MB) |
1.01x |
0.197 s |
0.106 s |
20.74x faster (source) |
| Tree Bitonic Sort | Sorting Networks | 5.719 s |
6.043 s
(146 MB) |
1.06x |
0.799 s |
0.450 s |
12.71x faster (source) |
| Breadth-First Search | Graph Traversal | 3.585 s |
3.918 s
(2.0 MB) |
1.09x |
0.343 s |
0.207 s |
17.32x faster (source) |
| N-Body Physics | Particle Gravity | 5.126 s |
5.758 s
(2.2 MB) |
1.12x |
0.516 s |
0.059 s |
86.88x faster (source) |
| Conway’s Game of Life | Cellular Automata | 6.776 s |
7.803 s
(2.1 MB) |
1.15x |
0.647 s |
0.063 s |
107.56x faster (source) |
| Mandelbrot Fractal | Complex Plane | 3.750 s |
4.473 s
(2.3 MB) |
1.19x |
0.395 s |
0.057 s |
65.79x faster (source) |
| Merkle Tree | Cryptographic Trees | 3.423 s |
4.222 s
(130 MB) |
1.23x |
0.422 s |
0.071 s |
48.21x faster (source) |
| 3D Raytracer | Computer Graphics | 3.646 s |
4.616 s
(2.1 MB) |
1.27x |
0.396 s |
0.151 s |
24.15x faster (source) |
| Levenshtein Edit Dist | Dynamic Prog. | 2.002 s |
2.559 s
(2.0 MB) |
1.28x |
0.236 s |
0.144 s |
13.90x faster (source) |
| Symbolic Regression | Genetic AST Search | 2.208 s |
3.014 s
(2.1 MB) |
1.37x |
0.266 s |
0.529 s |
8.30x (PAR-CPU) (source) |
| Parallel Radix Sort | Sorting Networks | 2.791 s |
4.083 s
(651 MB) |
1.46x |
0.445 s |
0.314 s |
8.89x faster (source) |
| N-Queens Solver | Combinatorics | 3.599 s |
5.406 s
(2.0 MB) |
1.50x |
0.455 s |
0.933 s |
7.91x (PAR-CPU) (source) |
| Regex Parallel Lexer | Text Processing | 1.036 s |
2.144 s
(2.0 MB) |
2.07x |
0.198 s |
1.075 s |
5.23x (PAR-CPU) (source) |
| HAMT Hashmap | Data Structures | 0.622 s |
2.741 s
(2.4 MB) |
4.41x |
0.238 s |
0.521 s |
2.61x (PAR-CPU) (source) |
3. Architectural Teardown: Two Surprising Benchmark Outcomes
3.1
Why Single-Core Bend 2 Beats Hand-Written C on tree-matmul
(2.064 s vs. 2.964 s)
How can a pure functional language beat clang -O3 C on
single-threaded quadtree matrix multiplication (benchmark dataset)?
- Packed Leaf Words vs. Tagged C Struct Pointers: In
Bend 2,
Leaf{v}packs its scalar payload inside the 64-bit word (term_aux(t) == CID_LEAF), returning immediately from registers without a memory load. - Worklist Jumps vs. 8-Way C Call Prologue/Epilogue:
Multiplying two 2 × 2 quadtree blocks
performs 8 recursive multiplications and 4 recursive additions. In C,
each recursive call spills registers and allocates a C stack frame; in
BendRT, tail calls reload registers in place (
WL_AGAIN) inside a single worklist function (bendlang/bend). - Zero-Cost Borrow Inference: BendRT proves statically that input matrix subtrees are read-only during multiplication, passing borrowed 64-bit words with zero reference-count traffic.
3.2
Why Single-Core C Beats Single-Core Bend 2 on hashmap
(0.622 s vs. 2.741 s) and lexer
(1.036 s vs. 2.144 s)
Conversely, C’s raw pointer model wins on flat, sequential memory layouts (benchmark dataset):
hashmap(4.41xC advantage on 1 core): C mutates an in-place array of 4,096 bucket pointers (node->next = bucket[i]). Bend 2 extracts a functional chain from an affineArray<Chain>, matchesCConsnodes, and writes back a freshly consed chain. Once Bend 2 enables 16 CPU cores (0.238 s), however, it overtakes sequential C by2.61x(benchmark dataset).lexer(2.07xC advantage on 1 core): C scans a contiguouschar buf[256]stack array (*p++), whereas Bend 2 walks an algebraic linked list of characters (List<Char>).
4. Embedding Bend 2 Inside C Applications via Foreign Effects & Shared Libraries
Because bend prog.bend -o prog.c outputs standard ISO
C11, C systems can embed verified Bend 2 modules directly (bendlang/bend):
# 1. Verify all inductive laws in 0.38 s
bend kernel_proof.bend --verdict
# 2. Emit standalone C11 code and compile as a shared library
bend kernel.bend -o kernel.c
clang -O3 -shared -fPIC -pthread kernel.c -o libbend_kernel.so
Bend 2’s foreign effect system (import "./io_shim.c")
compiles any def custom_effect(x: U32) -> IO(U32)
declaration into a direct C function call at the sequential
IO loop boundary. This gives C systems engineers a clean
division of labor: keep low-level socket buffers, mmap file
I/O, and hardware drivers in C, and offload parallel tree/graph kernels
and LAWS.bend-verified state machines to Bend 2 (benchmark dataset).