Continuiamo
la panoramica sulla tecnologia HotSpot, quella che dovrebbe una volta per
tutte far dormire sonni tranquilli agli sviluppatori Java per quel che
riguarda le prestazioni
Introduzione
Non è
la prima volta che scriviamo di HotSpot sulle colonne di Moka Byte, e la
ragione è più che ovvia: da “Java-dipendenti”, vediamo questo
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 (hmm…..!!).
Dopo gli articoli
precedenti sull’argomento, Giovanni Puliti mi ha chiesto di dare un’occhiata
ad un articolo pubblicato su Java Pro di Maggio, autore Paul Tyma, dal
titolo: Introduzione a HotSpot, e dopo un’attenta lettura ho trovato alcune
considerazioni e alcuni spunti che ritengo molto interessanti . Paul sottolinea
il fatto 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 spots appunto), e dopo averne individuato uno lo
ottimizza. Da quel momento in poi, la porzione di codice trattata dovrebbe
in teoria essere più veloce. Da queste posizioni generali, oltre
che da alcune considerazioni specifiche per Java, parte il progetto HotSpot.
Si fa notare
come , osservando attentamente la struttura di Java, si possano individuare
numerosi punti suscettibili di enormi ottimizzazioni.
HotSpot vs
JIT
Ma andando a
fondo, Hot Spot cos’è esattamente? In parole semplici possiamo dire
che si tratta di un interprete un po’ particolare, in quanto in possesso
di un sofisticato motore di ottimizzazione che si occupa di individuare
gli hot spots nel codice Java durante l’esecuzione. Detto così verrebbe
da pensare che in fondo JIT, in quanto compilatore, sia più efficiente
di qualsiasi interprete ottimizzato. Paul fa notare come in realtà
JIT abbia una serie di difetti rispetto a HotSpot: intanto un compilatore
“Just In Time” deve necessariamente effettuare la compilazione alla partenza
del programma, causando un tempo morto iniziale, che per programmi complessi
può diventare biblico, mentre un interprete parte immediatamente.
Nel caso di Hot Spot, l’ottimizzazione scelta su una porzione di codice
può anche essere la compilazione di questo (cioè una JITtizzazione)
però avremo, invece che una lunga pausa iniziale, molte piccole
pause intermedie, quasi sempre non percettibili. Per di più, l’ottimizzazione
effettuata da JIT ha delle limitazioni, in quanto quando si trova davanti
a caricamento dinamico di classi o “reflection” costringono JIT ad essere
conservativo nella sua ottimizzazione per evitare di generare codice poco
stabile. Non basta, JIT non sa 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 a portare via oltre l’80% del tempo di
elaborazione. In questo HotSpot presenta numerosi vantaggi, dalla partenza
rapida, all’individuazione dei punti critici, effettuando così una
ottimizzazione sul processo di ottimizzazione.
Considerazioni
sull’ottimizzazione di Java
Nel suo articolo,
Paul pone alcune questioni di non poco conto. Giustamente sostiene che
il caricamento dinamico (dynamic loading) e il meccanismo di “reflection”
rappresentano un punto di forza indiscutibile di Java, permettendo di costruire
del codice altamente riutilizzabile e in qualche modo “intelligente”. Per
contro, queste due possibilità diventano un incubo per l’ottimizzazione.
L’esempio portato è semplice ma molto chiaro: nel caso della porzione
di pseudo-codice seguente:
x=false;
if (x)
do this();
è facilmente
comprensibile che la seconda riga di codice non sarà mai eseguita,
e un profiler sarà in grado di ottimizzare il codice eliminandone
le parti inutili. Questo è però valido solo nel caso in cui
x sia una variabile locale, altrimenti c’è la possibilità
che qualche thread esterno possa modificare la variabile rendendola vera
e consentendo così l’esecuzione della porzione di codice successiva.
Quindi possiamo dire che sarebbe possibile eliminare lo “statement” in
esame quando:
-
siamo sicuri che
la nostra applicazione sia a Thread singolo
-
siamo sicuri che
il metodo che contiene le 2 righe di codice non viene mai chiamato ( controllando
quindi tutte le classi dell’applicazione)
-
siamo sicuri che
da nessuna parte viene fatta l’assegnazione A.x= qualcosa, dove A sia la
classe contenente il nostro codice.
Come si vede, la
mole di lavoro che un JIT deve compiere per poter stabilire se sia legittimo
eliminare la riga di “if” è enorme, e comunque richiede
che si abbiano a priori tutta una serie di informazioni sulla classe in
esame. E qui viene il bello; abbiamo prima sottolineato quanto sia potente
il meccanismo di caricamento dinamico delle classi in Java, ebbene, questa
funzionalità impedisce che un compilatore possa in anticipo avere
quelle informazioni necessarie per l’ottimizzazione. HotSpot risolve questi
problemi perché non richiede conoscenze a priori delle classi in
esame, infatti la sua analisi di ottimizzazione si basa su dati che ottiene
a run-time, quindi analizza una classe che sia già stata caricata
e per la quale possa quindi accedere a tutte le informazioni del caso.
Le due tecnologie
analizzate, cioè l’ottimizzazione statica a priori e l’ottimizzazione
adattiva rappresentano due approcci totalmente differenti al processo
di “profiling” del codice. Per di più, ladove enga richiesto, HotSpot
è in grado di de-ottimizzare parti di codici, come nel caso di un
metodo al quale sia stato applicato l’ ”in-lining”, e che a run-time subisca
un “overriding”. In questo caso HotSpot è in grado di de-ottimizzare
il codice evitando pericolosi effetti collaterali.
Garbage Collector
Nel prosieguo
del suo articolo Paul passa 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,
esatto. Generazionale perché gli oggetti vengono categorizzati in
base alla loro età, non cronologica, ma espressa in numero di passaggi
di GC. L’algoritmo utilizzato è per alcuni versi modulare, nel senso
che è possibile sostituirlo senza problemi nel momento in cui se
ne abbia a disposizione uno migliore.Il GC di HotSpot è incrementale.
Cioè? In generale durante l’esecuzione di una applicazione, i processi
in corso si bloccano e passano il controllo al GC, che svolge il suo lavoro
e poi ricede il controllo. Questo processo è responsabile dei momenti
di “guru meditation” ai quali talvolta si assiste durante l’esecuzione
di un programma Java. Il GC di HotSpot invece, svolge il proprio lavoro
un po’ alla volta, evitando lunghe pause applicative. Questo deve essere
costato molto in termini di sviluppo del GC, ma gli effetti benefici sono
più che evidenti.
Prestazioni
Questo è
un argomento che abbiamo già trattato più volte, ma l’evoluzione
di HotSpot di recente ha avuto delle impennate non indifferenti, per cui
vale la pena soffermarvisi ancora una volta. Nella primissima versione
di HotSpot, Sun stessa sottolineava come il profiler fosse consigliato
soprattutto per piattaforme server e, aggiungo io, quel soprattutto significava
in realtà “quasi esclusivamente”. Il processo di ottimizzazione
partiva dopo un certo “rodaggio” dell’applicazione, e cominciava a dare
qualche risultato dopo diversi minuti di utilizzo massiccio, rendendolo
così praticamente inutilizzabile per programmi client. Nella versione
integrata nel jdk1.3 però si parla di HotSpot versione “client”,
e in effetti dalle prove effettuate i miglioramenti sono abbastanza percettibili
( vedi articolo del mese scorso sul jdk1.3). E’ subito evidente l’assenza
delle fastidiose pause durante le normali elaborazioni di programma, e
questo è riconducibile al discorso fatto prima sul nuovo algoritmo
di garbage collector utilizzato, e gli effetti dell’ottimizzazione sono
percettibili dopo pochi secondi di utilizzo del programma, segno che i
metodi di ottimizzazione utilizzati sono stati sicuramente migliorati per
rendere il profiler utilizzabile a livello client. Peraltro mi preme sottolineare
una cosa che ritengo abbastanza importante: Hot Spot può sicuramente
fare salti da gigante per portare Java a prestazioni eccellenti, ma non
nascondiamoci dietro semplici problemi di compilazione per giustificare
alcune carenze di performance. E’ sicuramente vero che tutta la piattaforma
Java sia suscettibile di significativi miglioramenti, e questo è
evidente dal fatto che ad ogni nuova release, con la correzione di alcuni
bugs, le prestazioni ne risentono positivamente. Ad esempi, nel Jdk1.3
sono apprezzabili alcuni miglioramenti prestazionali delle Swing, indipendentemente
da HotSpot. Il garbage collector, per quanto molto più affidabile
di prima è ben lungi dal potersi considerare pienamente ottimizzato
e qualche ritocco è ancora necessario per le classi di input/output.
Abbiamo visto come , la reimplementazione delle classi matematiche abbia
portato ad un incredibile aumento di velocità di calcolo. E poi
non dimentichiamo quel 40% di prestazioni dovuto alla scrittura di buon
codice, in barba a qualsiasi profiler………………..
Conclusioni
Forse Hot Spot
ha raggiunto una certa maturità. Ci sono ancora buoni margini di
miglioramento, ma come ho già scritto, sono tanti e tali i punti
di intervento sulla piattaforma complessiva Java, che potrebbero apportare
benefici, che probabilmente alla fine l’importanza di HotSpot nel processo
di evoluzione di Java potrebbe essere ridimensionata all’interno di canoni
di normalità. |