Bend 2 vs. JavaScript (Node.js, Bun, V8): Single-Threaded Event Loops vs. Lock-Free Interaction Parallelism
An execution-level comparison of JavaScript (V8, JavaScriptCore,
Web Workers, SharedArrayBuffer) and Bend 2 (BendRT Task
Cube and zero-dependency JS target) across 16 Apple M4 Max
benchmarks.
Executive Summary: Bottom Line Up Front
JavaScript engines (Google V8 in Node.js/Deno and JavaScriptCore in Bun) excel at single-threaded Just-In-Time (JIT) compilation and asynchronous I/O, but their shared-nothing isolate architecture makes fine-grained multi-core and GPU parallelism expensive.
- Keep JavaScript (Node.js, Bun, Browser DOM) for
event-driven HTTP/WebSocket servers, SSR rendering, browser
interactivity, and single-threaded workloads dominated by flat hash-map
lookups (
Map, object property inline caches). - Use Bend 2 (
bend -onative binary or GPU kernel) whenever a CPU-bound or recursive computation blocks the JavaScript event loop. Spawningworker_threadsor Web Workers in JavaScript forces recursive trees and object graphs throughstructuredCloneserialization. By contrast, Bend 2’s BendRT Task Cube distributes 64-bit tasks across 16 CPU cores (PAR-CPU) and 16,384 GPU lanes (PAR-GPU) with zero serialization, zero locks, and zero garbage collection pauses (bendlang/bend). Across the 16 Apple M4 Max benchmarks, Bend 2’s 16-core CPU runtime outruns V8/Bun by 3.75x to 65.13x, and its GPU runtime outruns V8/Bun by up to 297.68x (benchmark dataset). - Use Bend 2’s JavaScript Target
(
bend prog.bend -o prog.js) when you want to ship mathematically proven, termination-checked algorithms (LAWS.bend/PROOF.bend) into browsers, Cloudflare Workers, or Node/Bun services with zero npm dependencies (bendlang/bend).
1.
The JavaScript Concurrency Wall: Event Loop Blocking &
structuredClone Overhead
Why does JavaScript struggle on parallel tree, graph, and simulation algorithms? The bottleneck is architectural:
1.1 One Thread Per Isolate
Every V8 or JavaScriptCore isolate runs user JavaScript on a single execution thread. If a request handler runs a 500 ms tree search, raytrace, or K-means clustering pass, the entire event loop stalls. That stall starves every concurrent HTTP socket, timer, and promise microtask in that process.
1.2
Why worker_threads and Web Workers Fail on Recursive
Trees
To use multiple CPU cores in Node.js or Bun, developers must spawn
worker_threads or Web Workers. Because each worker owns an
isolated V8/JSC heap, passing a recursive data structure (such as an
AST, Merkle tree, or BVH scene graph) between workers invokes the
Structured Clone Algorithm (structuredClone /
postMessage):
- Deep Copy Penalty:
structuredClonewalks the entire object graph on the sender thread, serializes it into an intermediate buffer, and allocates a fresh copy on the receiver thread’s heap. For fine-grained divide-and-conquer tasks (a b = f(left) f(right)), serialization takes 10x to 100x longer than the computation itself. - The
SharedArrayBufferEscape Hatch Breaks High-Level Ergonomics:SharedArrayBufferandAtomicsallow workers to share raw byte buffers without copying. However, using them requires manually flattening JavaScript objects into typed-array byte offsets and writing error-prone spinlocks or futexes (Atomics.wait/Atomics.compareExchange).
1.3 How BendRT Eliminates Both Serialization and Locks
In Bend 2, a parallel let binding (a b = f(x) g(y))
executes inside a single unified 8 TiB virtual address space
(MAP_NORESERVE) shared across all CPU threads and
unified-memory GPU warps (bendlang/bend):
- Every value is a 64-bit word
(
[rfc(1) | tag(7) | aux(16) | loc(40)]). - Forking a task pushes a 64-bit pointer into a lock-free ring buffer inside the 128 × 128 Task Cube (16,384 rings), copying zero heap bytes (benchmark dataset).
- Because BendTT enforces Church-Rosser confluence and affine variable usage (mechanized in 20,981 lines of Lean 4), two worker threads reducing different branches of a term can never observe a data race (bendtt.lean).
2. Empirical Benchmarks: V8/Bun vs. Bend 2 on Apple M4 Max
The table below compares JavaScript/TypeScript execution on V8/Bun
(ts_sec) against Bend 2’s sequential CPU
(SEQ-CPU), 16-core parallel CPU (PAR-CPU), and
Metal GPU (PAR-GPU) runtimes across 10 representative
workloads from /data/bend-ecosystem.json (benchmark dataset):
| Workload | Category | JS / TS (V8/Bun) | Bend SEQ-CPU (RSS) |
Bend PAR-CPU 16c (RSS) |
Bend PAR-GPU Metal |
PAR-CPU Speedup vs. JS | PAR-GPU Speedup vs. JS |
|---|---|---|---|---|---|---|---|
| Conway’s Game of Life | Cellular Automata | 18.754 s |
7.803 s
(2.1 MB) |
0.647 s
(2.7 MB) |
0.063 s |
28.99x |
297.68x (source) |
| 3D Raytracer | Computer Graphics | 23.039 s |
4.616 s
(2.1 MB) |
0.396 s
(2.6 MB) |
0.151 s |
58.18x |
152.58x (source) |
| Merkle Tree | Cryptographic Trees | 7.227 s |
4.222 s
(130 MB) |
0.422 s
(131 MB) |
0.071 s |
17.13x |
101.79x (source) |
| K-Means Clustering | Machine Learning | 17.455 s |
2.069 s
(23.5 MB) |
0.268 s
(24.1 MB) |
0.191 s |
65.13x |
91.39x (source) |
| N-Body Physics | Particle Gravity | 5.332 s |
5.758 s
(2.2 MB) |
0.516 s
(2.7 MB) |
0.059 s |
10.33x |
90.37x (source) |
| Tree Bitonic Sort | Sorting Networks | 33.910 s |
6.043 s
(146 MB) |
0.799 s
(156 MB) |
0.450 s |
42.44x |
75.36x (source) |
| Mandelbrot Fractal | Complex Plane | 3.837 s |
4.473 s
(2.3 MB) |
0.395 s
(2.9 MB) |
0.057 s |
9.71x |
67.32x (source) |
| Tree Matrix Multiply | Quadtree Algebra | 11.379 s |
2.064 s
(2.8 MB) |
0.226 s
(14.8 MB) |
0.231 s |
50.35x |
49.26x (source) |
| Regex Parallel Lexer | Text Processing | 3.543 s |
2.144 s
(2.0 MB) |
0.198 s
(3.0 MB) |
1.075 s |
17.89x |
3.30x (source) |
| HAMT Hashmap | Data Structures | 0.892 s |
2.741 s
(2.4 MB) |
0.238 s
(9.0 MB) |
0.521 s |
3.75x |
1.71x (source) |
Honest
Analysis: Where V8 Beats Sequential Bend (nbody,
mandelbrot, hashmap)
Look closely at the JS / TS
vs. Bend SEQ-CPU columns in the table above (benchmark dataset):
- On single-threaded scalar loops
(
mandelbrot:3.837 sJS vs.4.473 sSEQ-CPU;nbody:5.332 sJS vs.5.758 sSEQ-CPU) and single-threaded hash table lookups (hashmap:0.892 sJS vs.2.741 sSEQ-CPU), V8’s TurboFan JIT compiler actually beats single-core Bend 2! V8 unboxes IEEE-754 floats in tight loops and uses hidden-class inline caches for object properties. - However, V8 is trapped on 1 core unless you manually rewrite your
algorithm around
worker_threadsandSharedArrayBuffer. When Bend 2 switches toPAR-CPU(16 cores) orPAR-GPU(Metal), the unmodified source jumps from5.758 sto0.516 s(CPU) and0.059 s(GPU) onnbody(benchmark dataset). Onhashmap, it drops from2.741 sto0.238 s(CPU) (benchmark dataset). - While a typical Node.js or Bun process consumes 45 MB to 120
MB RSS at baseline, Bend 2’s native binaries execute
gameoflife,nbody,raytrace, andlexerin 2.0 MB to 3.0 MB RSS (benchmark dataset).
3.
Inside Bend 2’s JavaScript Compiler Target
(bend -o prog.js)
In addition to compiling to native C/CUDA/Metal
(bend prog.bend -o prog), Bend 2 includes a first-class
JavaScript code generator (js_book and js_lib
in bend2/comp.ts) (bendlang/bend):
# Emit a standalone executable JS file
bend src/engine.bend -o dist/engine.js
# Emit an ES module for import in Node.js, Bun, or Browser bundles
bend src/engine.bend -o dist/engine.mjs
How Bend 2 Values Map to Native JavaScript
The generated JavaScript has zero external runtime dependencies and maps BendTT types directly onto fast V8/JSC primitives (bendlang/bend):
| Bend 2 Construct | Emitted JavaScript Representation | Runtime Characteristics |
|---|---|---|
Algebraic Constructors
(Succ{pred}) |
Plain objects: {$: "Succ", pred} |
Monomorphic object shapes optimize cleanly in V8 hidden classes (source) |
Natural Numbers (Nat) |
Native JavaScript BigInt (0n,
1n, capped at 248 − 1) |
Exact arbitrary-precision integer arithmetic without float rounding (source) |
Machine Scalars (U32,
F32) |
(x >>> 0) unsigned 32-bit ints and
Math.fround(x) |
Unboxed SMI / double representation in V8 and JSC (source) |
Linear Array<T> |
Native JS Array ([]) with in-place index
assignment |
Zero-copy array mutation verified safe by BendTT’s affine checker (source) |
| Tail-Recursive Functions | Trampolined while loop (run_loop) |
Prevents RangeError: Maximum call stack size exceeded
on deep recursion (source) |
4. When to Choose JavaScript vs. Bend 2
| Engineering Scenario | Recommended Choice | Why |
|---|---|---|
| HTTP APIs, WebSockets, DOM UI, Edge Routing | JavaScript (Node / Bun) | Non-blocking epoll/kqueue event loop and
universal web ecosystem. |
| Multi-Core / GPU Tree, Graph, Physics, or Sorting Kernel | Bend 2 (bend -o Native / GPU) |
Up to 297.68x faster than V8 without
worker_threads or structuredClone overhead (source). |
| Browser or Edge Module Requiring Mathematical Correctness | Bend 2 (bend -o module.mjs) |
Prove invariants in 0.38 s via
LAWS.bend/PROOF.bend, then ship
zero-dependency JS (source). |
| High-Density CLI / Pre-Commit Hook / Agent Swarm Gate | Bend 2 (bend -o Native) |
3.11 ms startup and 2.66 MB RSS vs. 45 to 60 MB RSS for Node.js scripts (source). |