EN
Torna al blog
primi-passi-con-nodejs #5 / 10

Architettura: V8 e Modello I/O

Le fondamenta tecniche di Node.js: come il motore V8 e il sistema I/O lo rendono incredibilmente veloce.

· 4 min di lettura ·
v8 architettura io libuv
In breve:

La velocità di Node.js non è magica, ma il risultato di due pilastri: il motore V8 (esecuzione) e Libuv (gestione I/O).

Per padroneggiare Node.js non basta saper scrivere codice JavaScript; bisogna capire come questo codice viene eseguito “sotto il cofano”. In questo capitolo esploreremo i componenti che rendono Node.js incredibilmente performante.


Il Motore V8: Il Cuore Pulsante

Node.js non interpreta JavaScript riga per riga. Utilizza V8, lo stesso motore open source sviluppato da Google per Chrome.

Just-In-Time (JIT)

V8 trasforma il codice JavaScript in codice macchina in tempo reale, permettendo prestazioni simili ai linguaggi compilati (come C++).

Garbage Collection

Gestisce automaticamente la memoria per te, liberando le risorse non più utilizzate senza interventi manuali.

Curiosità:

V8 è scritto in C++. Questo significa che Node.js agisce come un “guscio” JavaScript attorno a un motore estremamente veloce scritto a basso livello.


Il layer Libuv: Il ponte verso l’OS

Mentre V8 esegue il codice, Node.js ha bisogno di interagire con il file system, la rete e i database. V8 non sa fare queste cose nativamente. Qui entra in gioco Libuv, una libreria scritta in C che funge da ponte.

Libuv è il componente che implementa il Modello I/O non bloccante, permettendo a Node.js di essere asincrono e scalabile.


Blocking vs Non-Blocking I/O

Questa è la differenza fondamentale che definisce Node.js rispetto ad altri ambienti tradizionali.

1. Modello Blocking (Bloccante)

In un modello bloccante, il programma si ferma e aspetta che l’operazione (es. lettura di un file) sia conclusa prima di passare alla riga successiva.

import fs from "node:fs"

// Il programma si ferma qui finché il file non è letto
const data = fs.readFileSync("/file.txt")
console.log(data)

2. Modello Non-Blocking (Non Bloccante)

Node.js delega l’operazione al sistema operativo e prosegue immediatamente. Quando l’operazione è pronta, viene eseguita una callback.

import fs from "node:fs"

// Node.js avvia la lettura e passa subito oltre
fs.readFile("/file.txt", (err, data) => {
  if (err) throw err
  console.log(data)
})
Standard Moderno:

Sebbene le callback siano le fondamenta, per scrivere codice leggibile ed elegante oggi si utilizzano le Promises e la sintassi async/await.

import fs from "node:fs/promises"

try {
  const data = await fs.readFile("/file.txt")
  console.log(data)
} catch (err) {
  console.error(err)
}

Mettiamolo alla prova: Sync vs Async

È ora di vedere la differenza con i nostri occhi. Creiamo una sottocartella per questi esperimenti:

# Dalla cartella progetti-nodejs creata in precedenza
mkdir architettura-test
cd architettura-test

Per ora non serve un package.json. Questi script semplici funzionano anche senza configurazione.

Proviamo a scrivere un file in due modi diversi per osservare il comportamento del thread principale.

Esempio Sincrono (sync.js)

Crea il file con il tuo editor preferito oppure da terminale:

# Linux/macOS
touch sync.js

# Windows (PowerShell)
New-Item sync.js -ItemType File

Node.js deve finire la scrittura prima di poter eseguire qualsiasi altra riga di codice.

import fs from "node:fs"

const text = "A\n".repeat(10 ** 6)
fs.writeFileSync("file.txt", text, "utf8")
console.log("Scrittura completata.")

console.log("Altro codice.")

Output:

Scrittura completata.
Altro codice.

Esempio Asincrono (async.js)

Node.js avvia la scrittura “in background” e prosegue immediatamente.

import fs from "node:fs"

const text = "A\n".repeat(10 ** 6)
fs.writeFile("file.txt", text, "utf8", (err) => {
  if (err) throw err
  console.log("Scrittura completata.")
})

console.log("Altro codice.")

Output:

Altro codice.
Scrittura completata.
Il segreto della velocità:

Nell’esempio asincrono, “Altro codice” appare prima. Node.js non è rimasto con le mani in mano ad aspettare il disco!

Per i più curiosi:

Anche se l’asincronia sembra “gratis”, Node.js impiega un minimo di tempo extra per coordinare i thread interni di Libuv. Tuttavia, poter gestire migliaia di utenti mentre il disco lavora è il vero vantaggio competitivo.

Consiglio: Abbiamo usato 10 ** 6 righe per velocità. Se vuoi “sentire” il blocco nel caso sincrono, prova ad aumentare a 10 ** 8.


Ora sai che V8 esegue il codice e Libuv gestisce l’I/O, ma chi coordina tutto questo senza bloccare il sistema? Nel prossimo articolo incontrerai l’ Event Loop: il direttore d’orchestra di Node.js.