Google ha rilasciato nelle scorse ore Chrome 91. Non si è trattato propriamente di un aggiornamento “wow” perché i tecnici dell’azienda di Mountain View hanno aggiunto ben poche nuove caratteristiche al browser.
Ce n’è però una che spicca su tutte e sulla quale vale la pena soffermarsi: il nuovo Chrome è più veloce rispetto alla precedente versione di circa il 23% per ciò che riguarda l’elaborazione del codice JavaScript.
È merito di una serie di migliorie applicate al motore opensource V8 per la gestione di JavaScript e WebAssembly. Chrome utilizza infatti il nuovo compiler Sparkplug ed effettua un’ottimizzazione del codice JavaScript in memoria.
Un compiler per JavaScript? Ma JavaScript non è un linguaggio interpretato?
Perché Chrome utilizza e migliora continuamente il suo compiler JavaScript. Differenze tra codice interpretato e compilato
JavaScript è un linguaggio che non può in alcun modo interagire a basso livello con il dispositivo utilizzato. Lato client, nel caso di JavaScript, il codice viene caricato ed eseguito dall’interprete contenuto nel browser.
Un compilatore traduce una serie di istruzioni scritte in un determinato linguaggio di programmazione (codice sorgente) in istruzioni di un altro linguaggio, ad esempio codice macchina o un linguaggio a più basso livello. Si ottiene così codice più ottimizzato a livello macchina ma la compilazione può richiedere molto tempo a seconda della dimensione del codice sorgente. Per le applicazioni web si tratta di un approccio che non è affatto pratico perché l’esperienza d’uso deve essere necessariamente fluida e veloce.
Un interprete esegue invece immediatamente il codice con un approccio più veloce ma allo stesso tempo molto meno efficiente. Il codice non è ottimizzato per la macchina in uso e risulterà decisamente più ridondante rispetto al codice compilato.
Un compilatore just-in-time (JIT) effettua una “traduzione dinamica” durante l’esecuzione del codice piuttosto che precedentemente. JIT effettua in tempo reale l’interpretazione e l’esecuzione del codice valutando, attraverso un componente chiamato monitor o profiler, quale viene utilizzato più spesso. In questo modo è possibile compilare il codice richiamato più di frequente e se la pipeline di JIT è impostata correttamente l’esecuzione del codice risulterà molto più efficiente nonostante l’overhead iniziale.
Google V8 è un motore Javascript opensource ad alte prestazioni scritto in C++ che può essere utilizzato sia lato client che lato server grazie a NodeJS.
Per massimizzare le prestazioni quando V8 compila il codice JavaScript il parser (il componente che svolge l’analisi sintattica del codice) provvede anche a genera un “albero sintattico”. Esso è la rappresentazione ad albero della struttura sintattica del codice JavaScript. L’interprete (Ignition) genera il cosiddetto bytecode (linguaggio intermedio più astratto tra il linguaggio macchina e il linguaggio di programmazione; utile perché riduce la dipendenza dallo specifico hardware) a partire dall’albero sintattico.
Il compilatore (Turbofan) acquisisce il bytecode e genera automaticamente codice macchina ottimizzato.
Negli anni ’90 l’utilizzo tipico del codice JavaScript sui siti web era limitato a una o due funzioni nel tag head e una manciata di codice incorporato in pagina, collegato ad esempio all’evento onclick o similari. Interpretare il codice era molto semplice e forniva prestazioni perfettamente adeguate ai casi d’uso di allora. Se si voleva ottenere qualcosa di più “ricco” si usavano applet Java o si ricorreva all’ormai defunto Flash.
Nel 2004 Google Maps è stata una delle prime applicazioni web a fare un utilizzo pesante di JavaScript aprendo gli occhi sulle sue potenzialità ed evidenziando però, allo stesso tempo, anche gli evidenti problemi prestazionali derivanti da un approccio interpretato.
Il motore V8, integrato in Chrome nel 2008, è risultato degli sforzi da allora compiuti per ottimizzare l’esecuzione di codice JavaScript.
Velocizzare l’esecuzione di codice JavaScript con Google Sparkplug
Con Chrome 91 Google pone una nuova importante pietra miliare.
Il motore V8 può oggi usare diversi compilatori per ottimizzare le varie fasi di esecuzione di JavaScript. Con Sparkplug viene trovato un equilibrio tra Ignition e Turbofan poiché viene generato codice macchina nativo senza dipendere dalle informazioni raccolte durante l’esecuzione del codice JavaScript.
Una novità che d’ora in avanti permette al motore V8 di iniziare l’esecuzione rapidamente generando codice già prestazionale. Qui sono spiegati gli aspetti più tecnici.
Il nuovo meccanismo chiamato short builtins e integrato in V8 si occupa invece, come accennato in precedenza, di ottimizzare la posizione in memoria del codice generato. Quando V8 genera del codice specifico per la CPU a partire da JavaScript lo dispone in memoria. Questo codice generato chiamerà spesso le “funzioni builtin“, piccoli frammenti di codice per la gestione di routine comuni.
Per alcune CPU chiamare funzioni che sono “lontane” dal codice generato può rendere vane le ottimizzazioni effettuate in hardware, come la logica legata all’esecuzione speculativa.
La soluzione consiste nel copiare le funzioni builtin nella stessa regione di memoria del codice generato.