javascript Beginner 4 min

Il motore JavaScript: come V8 esegue il tuo codice

Call Stack, Heap, compilazione JIT e le ottimizzazioni con le Hidden Classes

· 830 parole

Il browser non «comprende» JavaScript così com'è: serve un motore che lo traduca ed esegua. In Chrome e in Node.js quel motore si chiama V8, ed è scritto da Google.  Il "motore" lo possiamo immaginare come la cucina di un ristorante. Il tuo codice è la comanda che arriva, quello che vogliamo mangiare; la cucina la legge, la prepara e serve il piatto. Tutto quello che segue è ciò che accade «dietro le quinte» in cucina. Vediamo, senza tecnicismi, cosa succede davvero quando il tuo codice parte.

1. I due spazi di lavoro: Call Stack e Heap

Il Call Stack (la «pila delle chiamate») tiene traccia di quale funzione è in esecuzione in questo momento. Funziona come una pila di piatti: l'ultimo che appoggi sopra è il primo che togli. Quando una funzione ne chiama un'altra, la nuova va in cima; quando finisce, viene tolta. JavaScript ha una sola pila: fa una cosa alla volta.

Curiosità: se una funzione chiama sé stessa all'infinito, la pila si riempie e va in errore. Quell'errore si chiama «stack overflow»… sì, proprio come il famoso sito.

L'Heap (il «magazzino» della memoria) è invece dove vivono le cose grandi e di dimensione variabile: oggetti, array e funzioni. Il Call Stack conserva solo un riferimento, cioè un indirizzo che punta all'oggetto nel heap: è un grande magazzino con tanti scaffali, sulla scrivania dell'addetto al magazzino (lo stack) non c'è l'intero scatolone, ma un bigliettino con l'indirizzo dello scaffale sul quale è presente un oggetto,  quando ho bisogno di quell'oggetto, seguo l'indirizzo indicato.

Fonte: MDN Web Docs: «Memory management» e glossario «Call stack».

2. La compilazione JIT: tradurre «al volo»

Il computer capisce solo il linguaggio macchina, non JavaScript. V8 lo traduce mentre il programma gira: questa tecnica si chiama JIT, Just-In-Time («appena in tempo»).

All'inizio tutto il codice viene tradotto in fretta in bytecode ed eseguito da un interprete (Ignition). Intanto V8 osserva come si comporta il programma. Se un pezzo di codice viene eseguito moltissime volte, diventa «caldo»: V8 lo passa a compilatori che lo ottimizzano sempre di più: Sparkplug, Maglev e infine TurboFan. Più si avanza, più tempo serve per compilare, ma il codice diventa molto più veloce.

Fonte: V8: «Maglev: V8's Fastest Optimizing JIT» e «Sparkplug» (v8.dev).

3. Le Hidden Classes: il trucco per andare veloce

In JavaScript gli oggetti sono flessibili: puoi aggiungere proprietà quando vuoi. Comodo, ma questa libertà rende lento ritrovare le proprietà, come cercare ogni volta in un dizionario. V8 risolve con le Hidden Classes (dette anche «shape», forme).

Quando crei più oggetti con le stesse proprietà, nello stesso ordine, V8 assegna loro la stessa forma nascosta. Così sa già dove trovare ogni proprietà, senza cercarla ogni volta: accesso quasi istantaneo.

// STESSA forma -> stessa hidden class, veloci 
const p1 = { x: 1, y: 2 }; 
const p2 = { x: 3, y: 4 }; // forma DIVERSA -> V8 crea un'altra hidden class 
const p3 = { y: 6, x: 5 }; // ordine invertito 
p1.z = 9; // proprieta' aggiunta dopo

Hidden Classes è come un modulo prestampato. Se tutti compilano lo stesso modulo con i campi nello stesso ordine (nome, cognome, età), sai già che, per esempio, l'età è sulla terza riga. Se invece ognuno scrive i dati alla rinfusa su un foglio bianco, devi rileggere tutto ogni volta.

Regola pratica: crea gli oggetti sempre con le stesse proprietà nello stesso ordine (idealmente tutte nel costruttore) ed evita di aggiungerle dopo o di usare delete. Così condividono la stessa hidden class e restano veloci.

Fonte: V8: «Fast properties in V8» (v8.dev): le HiddenClasses identificano la forma di un oggetto.

4. La deottimizzazione: quando V8 fa marcia indietro

Il codice super-ottimizzato si basa su assunzioni: «questo oggetto avrà sempre questa forma», «questa variabile sarà sempre un numero». V8 aggiunge una piccola guardia che controlla se l'assunzione regge.

Se a un certo punto la rompi: cambi la forma dell'oggetto, passi una stringa dove prima c'era un numero: la guardia scatta e avviene la deoptimization: V8 butta via il codice ottimizzato e torna all'interprete, più lento ma sempre corretto. Poi, se il codice si stabilizza, può ri-ottimizzare.

Se a un cuoco, che aveva imparato la ricetta a memoria, all'improvviso gli cambio un ingrediente fondamentale, deve fermarsi e riprendere in mano il ricettario scritto; dovrà rileggere la ricetta e la eseguirà correttamente, ma ci impegherà più tempo.

Morale: il codice prevedibile (tipi e forme stabili) è codice veloce. Cambiare di continuo tipi e forme costringe V8 a deottimizzare.

In sintesi

Pezzo Cos'è In una frase
Call Stack La pila delle chiamate Segna quale funzione gira ora, una alla volta
Heap Il magazzino della memoria Dove vivono oggetti, array e funzioni
JIT Traduzione «al volo» Compila in linguaggio macchina mentre il programma gira
Hidden Classes Le «forme» degli oggetti Stessa forma = accesso alle proprietà velocissimo
Deoptimization La marcia indietro Se salta un'assunzione, V8 torna alla modalità sicura e lenta

Non serve conoscere V8 nei dettagli per scrivere JavaScript. Ma sapere che «premia la coerenza» ti aiuta a scrivere codice più veloce quasi senza sforzo: oggetti dalla forma stabile, tipi prevedibili, il motore farà il resto.

Per approfondire

Accedi o registrati per votare, salvare o commentare la pillola

Commenti (0)

Registrati per commentare, fare domande e proporre l'argomento della prossima pillola (oppure accedi se hai già un account).

Nessun commento. Sii il primo a scrivere qualcosa.

Pillole correlate