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