Generare codice javascript ottimizzato per lo startup e il JIT con Haxe

Negli ultimi anni Javascript è diventato un linguaggio molto veloce grazie ai JIT (Just In Time compiler) che permettono di analizzare e ottimizzare il codice a runtime durante l’esecuzione del programma.

Ottimizzazione che non avviene subito, in quanto il JIT deve essere “scaldato” per poter ottimizzare il codice.

Se dobbiamo scrivere programmi che hanno un ciclo di vita breve e vengono richiamati spesso o che necessitano da subito della massima performance (programmi da shell, widgets o banner) il JIT non ci può aiutare a ottenere il massimo della velocità.

In questo caso possiamo provare a usare linguaggi o tools che ottimizzano il codice nel momento in cui viene generato.

Tra i tools javascript esiste Closure di google che nell’advanced mode permette di fare inline function e dead code elimination.
Haxe invece è un linguaggio che compila in javascript (oltre che in altri 10 linguaggi) che ha una serie di features per generare codice molto ottimizzato per lo startup e piu’ facilmente “digeribile” dal JIT.

Vediamo qualcuna di queste features:

Static type system

Haxe ha un type system statico come Typescript, Java… che permette di generare codice facilmente ottimizzabile dal JIT.
Questo perchè anche se le informazioni del type system “evaporano” quando viene generato il codice lasciano comunque un’impronta, uno schema.
Le variabili non saranno riassegnate ad altri tipi, e le funzioni saranno sempre richiamate rispettando la signature e i tipi degli argomenti.
Questi controlli fatti dal type system generano codice più facilmente digeribile dal JIT.

Dead Code Elimination

Avendo un type system statico e conoscendo la struttura di tutto il programma, Haxe può eliminare tutte le funzioni e le le classi delle librerie caricate come dipendenze che non sono state usate.

Per esempio immaginiamo di avere una libreria molto complessa che contiene centinaia di classi, e di queste dobbiamo chiamare solo un metodo statico di una di queste. In questo caso il compilatore inserirà nel codice generato solo questa funzione ed eventualmente altre funzioni richiamate.

Questo permette di generare file javascript con poco peso.

Inline function

Definendo una funzione inline Haxe andrà a sostituire tutte le chiamate a quella funzione con il suo body.
Questo ottimizza molto la velocità di esecuzione, in quanto ogni chiamata a una funzione ha un peso in termini di tempo. Peso che varia in base a quanto quella funzione viene richiamata (immaginate di chiamare una funzione dentro a un loop).

Una caratteristica molto potente dell’inliner di haxe è la possibilità di poter fare l’inline anche di funzioni anonime.
Se ad esempio si passa una funzione anonima a un metodo come map, il body di questa verrà inserito nell’inlining del metodo map.

Inline constructor

A volte capita di dover usare oggetti solo in un contesto locale, senza la necessità di doverli passare ad altre funzioni.
Definendo inline un constructor Haxe non genererà un nuovo oggetto, ma riscriverà le proprietà dell’oggetto come variabili dello scope locale, mantenendone traccia per la chiamata dei metodi stessi.

Static Analyzer

Haxe ha anche un potente static analyzer che ottimizza ancora di più il codice per una serie di casi:
- variabili inutilizzate
- assegnazione a variabili temporanee superflue
- generazione di scope inutili
- cancellazione di branch di if e switch inutilizzati
- inline di valori e sostituzioni di codice quanto questi fanno riferimento a valori immutabili desumibili a compile time.
Per esempio var a = 1,b =2,c = a+b; diverrà 3.

Macro

Questa è una delle feature più esoteriche di Haxe, e si esce dall’ambito della programmazione per entrare nella metaprogrammazione.
Attraverso le macro andiamo a modificare il compilatore, per istruirlo su come generare nuovo codice, permettendo di astrarre e di automatizzare molte operazioni ripetitive che siamo abituati a scrivere ogni volta.
A differenza di altri linguaggi, il codice generato dalle macro sarà sempre validato dal type system dandoci un ulteriore livello di controllo sul codice.