V8 è un motore javascript creato presso il centro di sviluppo di Google in Germania. Il motore è open source e scritto C++. Viene utilizzato sia lato client (Google Chrome) che lato server (node.js), nell'ecosistema delle applicazioni JavaScript.

V8 è stato concepito per la prima volta per aumentare le prestazioni di runtime JavaScript nei browser. Per aumentare la velocità, V8 traduce il codice JavaScript in un linguaggio macchina ad alte prestazioni invece di utilizzare un interprete. Il motore compila il codice Javascript in assembly in fase di esecuzione implementando a Compilatore JIT (Just-In-Time). come fanno la maggior parte dei moderni motori Javascript (come SpiderMonkey o Rhino (Mozilla)). La principale differenza con V8 è che non produce bytecode/codice intermedio.
Lo scopo di questo articolo è mostrarti e farti capire come funziona il motore V8 per produrre codice ottimizzato per il lato client e il lato server delle tue applicazioni. Se ti sei mai posto la domanda "Dovrei essere preoccupato per le prestazioni di JavaScript", allora ti rispondo con una citazione di Daniel Clifford (tech lead e manager del team V8):
In francese:
"Non è solo per rendere più veloce la tua app attuale, è per rendere possibili cose che prima non erano possibili"
In inglese:
"Non si tratta solo di rendere più veloce l'esecuzione della tua attuale applicazione, ma di abilitare cose che non sei mai stato in grado di fare in passato".

Classe nascosta
JavaScript è basato su prototipi: non ci sono classi e gli oggetti vengono creati utilizzando un processo di clonazione. JS è anche un linguaggio tipizzato dinamicamente: i tipi e le relative informazioni non sono autoesplicativi e le proprietà possono essere aggiunte e persino rimosse dagli oggetti al volo. L'accesso efficiente a tipi e proprietà è una prima grande sfida per V8. Invece di utilizzare una struttura simile a un dizionario per contenere le proprietà degli oggetti ed eseguire una ricerca dinamica per trovare la posizione di una proprietà (come fanno la maggior parte dei motori JavaScript), V8 crea classi nascoste, in runtime, in modo da avere una rappresentazione interna del digitare il sistema e migliorare i tempi di accesso alla proprietà.
Prendi come esempio una funzione Point e la creazione di due oggetti Point:

Se i layout sono gli stessi, quali sono, p et q fanno parte della stessa classe nascosta creata da V8. Ciò dimostra un altro vantaggio dell'utilizzo delle classi nascoste: consentono a V8 di raggruppare oggetti con le stesse proprietà. Qua; p et q usa lo stesso codice ottimizzato.
Supponiamo ora di voler aggiungere una proprietà z al nostro oggetto q, subito dopo la sua dichiarazione (cosa assolutamente normale con un linguaggio tipizzato dinamicamente).
In che modo V8 gestisce questo scenario? In realtà, V8 crea una nuova classe nascosta ogni volta che la funzione di costruzione dichiara una proprietà e controlla le modifiche nella classe nascosta. Come mai ? Perché se si creano due oggetti (p et q) e se un membro viene aggiunto al secondo oggetto (q) dopo la sua creazione, V8 deve mantenere l'ultima classe nascosta creata (per il primo oggetto p) e il motore deve crearne uno nuovo con (per il secondo oggetto q) il nuovo membro.

Ogni volta che viene creata una nuova classe nascosta, la precedente viene aggiornata con una transizione di classe che indica quale classe nascosta utilizzare al posto della precedente.
Ottimizzazione del codice
Poiché V8 crea una nuova classe nascosta per ogni proprietà, le loro creazioni dovrebbero essere ridotte a icona. Per questo, dobbiamo cercare di non aggiungere proprietà dopo la creazione dell'oggetto e inizializzare sempre i membri dell'oggetto nello stesso ordine (per evitare diversi alberi di classe nascosti).
Un altro consiglio: le operazioni monomorfiche sono quelle che funzionano su oggetti che condividono la stessa classe nascosta. V8 crea una classe nascosta quando si chiama una funzione. Se chiamiamo la stessa funzione ma con parametri di tipo diverso, V8 deve creare un'altra classe nascosta: Preferisci il codice monomorfico al codice polimorfico.
Altri esempi di come V8 ottimizza il codice JavaScript
Valori contrassegnati
Per avere una rappresentazione efficiente di numeri e oggetti JavaScript, V8 li rappresenta con un valore di 32 bit. Usa un po' per sapere se è un oggetto (flag=1) o un intero (flag=0) chiamato da SMall Integer o SMI a causa di quei 31 bit. Quindi, se un valore numerico è maggiore di 31 bit, V8 avvolgerà il numero, rendendolo doppio o creando un nuovo oggetto per avvolgerlo.
Ottimizzazione del codice: utilizza il più possibile numeri con segno a 31 bit per evitare la costosa operazione di incapsulamento in un oggetto JavaScript.
Tabelle
V8 utilizza due metodi diversi per elaborare gli array:
- Elementi veloci: Progettato per schede in cui tutti i tasti sono molto compatti. Ne hanno uno buffer di memoria lineare in modo che l'accesso sia molto efficiente.
-
Voci del dizionario: Progettato per schede in cui gli elementi sono sparsi. Lo sono infatti tabelle hash, a cui è più costoso accedere rispetto a "Fast Elements".
Ottimizzazione del codice: Assicurati che V8 utilizzi il metodo "Fast Element" per gestire gli array, in altre parole, evita gli array con elementi sparsi in cui le chiavi non sono ordinate in modo incrementale. Evitare inoltre di pre-allocare array di grandi dimensioni. È meglio che crescano man mano che procedono. Infine, non eliminare gli elementi negli array: tutte le chiavi verrebbero quindi disperse.
In che modo V8 compila il codice JavaScript?
V8 ha due compilatori!
- Un Compilatore "completo". che può generare un buon codice per qualsiasi JavaScript: buon codice ma non è un ottimo codice JIT. Lo scopo di questo compilatore è generare codice rapidamente. Per raggiungere questo obiettivo, il compilatore non esegue l'analisi dei tipi e non sa nulla sui tipi. Il compilatore usa invece una strategia di cache in linea o "IC" per affinare la conoscenza dei tipi mentre il programma è in esecuzione. IC è molto efficiente e aumenta la velocità (di un ordine x20).
-
Un ottimizzazione del compilatore che produce codice fantastico per la maggior parte del linguaggio JavaScript. Questo compilatore viene dopo e ricompila le funzioni che vengono utilizzate più volte (funzioni calde). Questo compilatore prende i tipi dalla cache inline e decide come ottimizzare al meglio il codice. Tuttavia, alcune parti della lingua non sono ancora supportate, ad esempio i blocchi try/catch. (Il trucco per i blocchi try/catch è scrivere il codice "unstable" in una funzione e chiamarlo nel blocco try).
Ottimizzazione del codice: Supporta anche V8 de-ottimizzazione: il compilatore di ottimizzazione fa ipotesi ottimistiche dalla cache inline su tipi diversi, la deottimizzazione si verifica se queste ipotesi non sono valide. Ad esempio, se è stata creata una classe nascosta non prevista, V8 elimina il codice ottimizzato e torna al compilatore "Completo" per recuperare i tipi dalla cache inline. Questo processo è lento e dovrebbe essere evitato, il che è possibile evitando di modificare le funzioni dopo che sono state ottimizzate.
Risorse
- Google I/O 2012 “Breaking the JavaScript Speed Limit with V8” con Daniel Clifford, tech lead e manager del team V8: video and diapositive.
-
V8: un motore JavaScript open source: video di Lars Bak, ingegnere di base del V8.
-
Post del blog di Nikkei Electronics Asia: Perché il nuovo motore Google V8 è così veloce?
Risorse addizionali: monomorfico vs polimorfico
Fonte : Come funziona il motore V8?, Thibalt Laurens
Traduzione di Yoan Ribeiro
