Introduzione
Non è la prima volta che scriviamo di HotSpot sulle colonne di MokaByte, e la ragione è più che ovvia: da Java-dipendenti vediamo questa tecnologia come la panacea a tutti i nostri mali e, in attesa di avere veramente una Virtual Machine oscenamente performante, ne seguiamo l'evoluzione con costanza e pazienza.
Dopo gli articoli precedenti sull'argomento, Giovanni Puliti mi ha chiesto di dare un'occhiata a un articolo di Paul Tyma pubblicato su Java Pro di maggio, dal titolo "Introduzione a HotSpot". Dopo un'attenta lettura ho trovato alcune considerazioni e spunti molto interessanti. Paul sottolinea che il progetto HotSpot nasce da anni di studi accademici su interpreti auto-ottimizzanti. In particolare fa riferimento a Self, sviluppato dalla Stanford University, che a runtime controlla quali punti impegnino le maggiori risorse CPU, gli hot spot appunto, e dopo averne individuato uno lo ottimizza. Da quel momento la porzione di codice trattata dovrebbe essere più veloce.
Si fa notare come, osservando attentamente la struttura di Java, si possano individuare numerosi punti suscettibili di enormi ottimizzazioni.
HotSpot vs JIT
Ma che cos'è esattamente HotSpot? In parole semplici è un interprete particolare, dotato di un sofisticato motore di ottimizzazione che individua gli hot spot nel codice Java durante l'esecuzione. Detto così verrebbe da pensare che un JIT, in quanto compilatore, sia più efficiente di qualsiasi interprete ottimizzato.
Paul fa notare che il JIT ha una serie di difetti rispetto a HotSpot. Un compilatore Just In Time deve effettuare la compilazione alla partenza del programma, causando un tempo morto iniziale che per programmi complessi può diventare enorme, mentre un interprete parte immediatamente. Nel caso di HotSpot, l'ottimizzazione scelta su una porzione di codice può anche essere la sua compilazione, cioè una JITtizzazione, ma invece di una lunga pausa iniziale avremo molte piccole pause intermedie, quasi sempre non percepibili.
Inoltre l'ottimizzazione effettuata da un JIT ha delle limitazioni: il caricamento dinamico di classi o la reflection costringono il compilatore a essere conservativo per evitare di generare codice poco stabile. Il JIT non sa poi quali punti sia conveniente ottimizzare e quali no, quindi effettua una compilazione indiscriminata che porta via parecchio tempo, quando è ormai assodato che in media il 20% del codice di un programma occupa oltre l'80% del tempo di elaborazione. HotSpot presenta numerosi vantaggi, dalla partenza rapida all'individuazione dei punti critici, effettuando così una ottimizzazione dello stesso processo di ottimizzazione.
Considerazioni sull'ottimizzazione di Java
Il caricamento dinamico e il meccanismo di reflection rappresentano un punto di forza indiscutibile di Java, permettendo di costruire codice altamente riutilizzabile e in qualche modo intelligente. Per contro, queste possibilità diventano un incubo per l'ottimizzazione. Consideriamo il seguente pseudocodice:
x = false; if (x) doThis();
È facile comprendere che la seconda riga non sarà mai eseguita e un profiler potrebbe eliminarla. Questo è però valido solo se x è una variabile locale; altrimenti qualche thread esterno potrebbe modificarla, rendendola vera e consentendo l'esecuzione della porzione successiva. Sarebbe possibile eliminare lo statement soltanto quando:
1. siamo sicuri che la nostra applicazione sia a thread singolo; 2. siamo sicuri che il metodo che contiene le due righe non venga mai chiamato, controllando tutte le classi dell'applicazione; 3. siamo sicuri che da nessuna parte venga fatta un'assegnazione A.x = qualcosa, dove A sia la classe contenente il codice.
La mole di lavoro che un JIT deve compiere per stabilire se sia legittimo eliminare la riga di if è enorme e richiede a priori molte informazioni sulla classe. Il caricamento dinamico impedisce al compilatore di possedere in anticipo tali informazioni. HotSpot risolve il problema perché non richiede conoscenze a priori: la sua analisi si basa su dati ottenuti a runtime, quindi su una classe già caricata, per la quale può accedere a tutte le informazioni.
L'ottimizzazione statica a priori e quella adattiva rappresentano due approcci totalmente differenti al profiling del codice. Dove richiesto, HotSpot è anche in grado di de-ottimizzare parti di codice: ad esempio un metodo al quale sia stato applicato l'inlining e che a runtime subisca un overriding. In questo caso HotSpot de-ottimizza evitando pericolosi effetti collaterali.
Garbage Collector
Paul passa poi in esame alcune caratteristiche del garbage collector di HotSpot, sottolineando come secondo lui sia il più avanzato in circolazione. Il GC della VM Sun classica è di tipo generazionale; quello di HotSpot è generazionale, incrementale ed esatto.
È generazionale perché gli oggetti vengono categorizzati in base alla loro età, non cronologica ma espressa in numero di passaggi del GC. L'algoritmo è per alcuni versi modulare, nel senso che è possibile sostituirlo senza problemi quando se ne abbia a disposizione uno migliore.
Il GC di HotSpot è inoltre incrementale. In generale, durante l'esecuzione di un'applicazione, i processi si bloccano e passano il controllo al garbage collector, che svolge il proprio lavoro e poi restituisce il controllo. Questo processo è responsabile delle lunghe pause alle quali talvolta si assiste durante l'esecuzione di un programma Java. Il GC di HotSpot svolge invece il proprio lavoro un po' alla volta, evitando lunghe pause applicative.
Prestazioni
L'evoluzione recente di HotSpot ha avuto impennate non indifferenti. Nella primissima versione Sun consigliava il profiler soprattutto per piattaforme server; quel "soprattutto" significava in realtà quasi esclusivamente. Il processo di ottimizzazione partiva dopo un certo rodaggio dell'applicazione e cominciava a dare risultati dopo diversi minuti di utilizzo massiccio, rendendolo praticamente inutilizzabile per programmi client.
Nella versione integrata nel JDK 1.3 si parla invece di HotSpot versione client e dalle prove i miglioramenti sono percettibili. È evidente l'assenza delle fastidiose pause durante le normali elaborazioni, riconducibile al nuovo garbage collector, e gli effetti dell'ottimizzazione si avvertono dopo pochi secondi di utilizzo: segno che i metodi sono stati migliorati per rendere il profiler utilizzabile a livello client.
HotSpot può sicuramente fare salti da gigante per portare Java a prestazioni eccellenti, ma non bisogna nascondere dietro semplici problemi di compilazione tutte le carenze di performance. L'intera piattaforma Java è suscettibile di miglioramenti, come dimostra il fatto che a ogni nuova release la correzione di bug ha effetti positivi. Nel JDK 1.3, ad esempio, sono apprezzabili miglioramenti prestazionali delle Swing indipendenti da HotSpot. Il garbage collector è ancora perfettibile e qualche ritocco è necessario per le classi di input/output. La reimplementazione delle classi matematiche ha portato a un incredibile aumento della velocità di calcolo. E non dimentichiamo quel 40% di prestazioni dovuto alla scrittura di buon codice, in barba a qualsiasi profiler.
Conclusioni
Forse HotSpot ha raggiunto una certa maturità. Ci sono ancora buoni margini di miglioramento, ma sono tanti i punti di intervento sulla piattaforma Java complessiva che potrebbero apportare benefici; probabilmente, alla fine, l'importanza di HotSpot nel processo di evoluzione di Java potrebbe essere ridimensionata all'interno di canoni di normalità.
