Understanding I/O Strength vs CPU Weakness β and How to Solve It
This project provides interactive JavaScript examples demonstrating the Node.js performance paradox and its solutions through Worker Threads and Native Modules.
- The Paradox: Why Node.js excels at I/O but struggles with CPU tasks
- Event Loop Deep Dive: Understand every phase (nextTick, Promise, timers, I/O, setImmediate)
- Worker Threads: Offloading CPU work to parallel JavaScript threads
- Worker Pools: Production-ready patterns with task queues
- Native Modules: Maximum performance with Rust/C++ integration
- WebAssembly: Portable, sandboxed alternative to native addons
- Ecosystem Comparison: When to choose Native vs WASM
- Ultimate Architecture: Main Thread β Worker Pool β Native Core
# START HERE - The Ultimate Showdown
npm start # Watch I/O vs CPU in real-time!
# Or run demos individually
npm run demo:showdown # 0. I/O vs CPU showdown with live requests
npm run demo:deep-dive # 1. Event loop phases explained
npm run demo:blocking # 2. See the event loop blocking problem
npm run demo:worker-threads # 3. Solution A: Worker Threads
npm run demo:worker-pool # 4. Production Worker Pool pattern
npm run demo:native-addon # 5. Solution B: Native Modules
npm run demo:wasm # 6. WebAssembly demos
npm run demo:ecosystem # 7. Native vs WASM comparison
npm run demo:architecture # 8. The Ultimate Architecture
# Run benchmarks
npm run benchmark
# Open interactive visualization
npm run visualizeβββ examples/
β βββ 00-io-vs-cpu-showdown.js # β THE showcase - I/O vs CPU with live requests
β βββ 01-event-loop-deep-dive.js # Event loop phases explained
β βββ 01-blocking-event-loop.js # The problem: CPU blocks event loop
β βββ 02-worker-threads-solution.js # Solution A: Worker Threads
β βββ 03-worker-pool.js # Production Worker Pool pattern
β βββ 04-native-addon-demo.js # Solution B: Native Modules
β βββ 05-wasm-demo.js # WebAssembly alternative
β βββ 06-ecosystem-comparison.js # Native vs WASM comparison
β βββ 07-ultimate-architecture.js # Complete architecture demo
βββ benchmarks/
β βββ run-all.js # Performance benchmark suite
βββ visualization/
β βββ index.html # Interactive web visualization
βββ package.json
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β EVENT LOOP β
β β
β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β
β β Timer ββββββΆβ I/O ββββββΆβ Check ββββββΆβ Close β β
β β Callbacksβ β Callbacksβ β (setImm) β β Callbacksβ β
β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β
β β² β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
STRENGTH: Non-blocking I/O (network, disk, database)
β WEAKNESS: CPU-bound tasks BLOCK the entire loop
import { Worker, isMainThread, parentPort } from 'worker_threads';
// Main thread stays free for I/O
const worker = new Worker('./cpu-task.js');
worker.postMessage({ data: heavyData });
worker.on('message', result => {
// CPU work done in background!
});// Using sharp (native C++ image processing)
import sharp from 'sharp';
await sharp('photo.jpg')
.resize(800, 600)
.webp({ quality: 85 })
.toFile('output.webp');
// 50x faster than pure JavaScript!| Criteria | Native Addons | WebAssembly |
|---|---|---|
| β‘ Performance | 100% (Maximum) | 70-90% (Near-native) |
| π Portability | 35% (Per-platform) | 100% (Universal) |
| π Safety | 50% (Full OS access) | 100% (Sandboxed) |
| π§ OS Access | 100% (Full) | 15% (Limited/WASI) |
| π Browser | 0% (Node only) | 100% (Everywhere) |
INCOMING REQUESTS
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MAIN THREAD (Event Loop) β
β HTTP Routing β WebSocket Management β Response β
β β‘ MUST STAY LIGHTWEIGHT - No CPU work here! β
βββββββββββββββββββββββββββββ³βββββββββββββββββββββββββββββ
β
postMessage() / IPC
β
ββββββββββββββββββββββββββββ»ββββββββββββββββββββββββββββββ
β WORKER POOL β
β ββββββββββ ββββββββββ ββββββββββ ββββββββββ β
β βWorker 1β βWorker 2β βWorker 3β βWorker Nβ Γ CPUs β
β β β β β β β β β β
β β ββββββ β β ββββββ β β ββββββ β β ββββββ β β
β β βRUSTβ β β βRUSTβ β β βRUSTβ β β βRUSTβ β β
β β β π β β β β π β β β β π β β β β π β β β
β β ββββββ β β ββββββ β β ββββββ β β ββββββ β β
β ββββββββββ ββββββββββ ββββββββββ ββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- sharp - Image processing (libvips)
- bcrypt - Password hashing
- better-sqlite3 - SQLite database
- node-canvas - 2D graphics
- @ffmpeg/ffmpeg - Video processing
- @duckdb/duckdb-wasm - Analytics database
- esbuild-wasm - JavaScript bundler
- sql.js - SQLite in WASM
use napi_derive::napi;
#[napi]
pub fn count_primes(max: u32) -> u32 {
(2..=max).filter(|&n| is_prime(n)).count() as u32
}Napi::Number CountPrimes(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
uint32_t max = info[0].As<Napi::Number>().Uint32Value();
return Napi::Number::New(env, count_primes(max));
}Run npm run benchmark to see results on your machine:
βββββββββββββββββββββββ¬βββββββββββββββ¬ββββββββββββββ¬ββββββββββββββββββββββ
β METHOD β TOTAL TIME β SPEEDUP β EVENT LOOP β
βββββββββββββββββββββββΌβββββββββββββββΌββββββββββββββΌββββββββββββββββββββββ€
β β Blocking β 2000ms β 1.0x β β BLOCKED β
β β
Worker Threads β 600ms β 3.3x β β
FREE β
β π Native (est.) β 80ms β 25.0x β β
FREE β
βββββββββββββββββββββββ΄βββββββββββββββ΄ββββββββββββββ΄ββββββββββββββββββββββ
- Node.js Worker Threads Documentation
- Piscina - Production Worker Pool
- NAPI-RS - Rust to Node.js
- WebAssembly on Node.js
MIT