MokaByte
Numero 29 - Aprile 1999
|
|||
|
di codice Java |
||
di
Andrea Fasce |
|
||
Alcuni trucchi e soluzioni per rendere Java un po' meno lento o perlomeno per evitare di rallentarlo troppo |
IntroduzioneSpesso mi e’ capitato di scontrarmi (bonariamente di intende….:) con alcuni amici sull’efficienza e velocita’ del java in applicazioni realtime tanto che dopo l’ennesima discussione ho deciso di dimostrare le mie tesi programmando qualcosa che non avrebbe lasciato possibilita’ di risposta da parte dei miei interlocutori: una engine 3d completamente software; il mio obiettivo era quello di ottenere una applet compatta e veloce, che fosse compatibile con tutti i browsers e che fosse facile da usare; e finalmente dopo molto tempo passato a programmare, ma soprattutto a cercare vie alternative per ottenere algoritmi efficienti, ho sviluppato un kit di sviluppo completo per importare scene create con il 3dsmax e con il vrml (e presto anche con il lightwave) dentro ad una applet.Molti sono i problemi di efficienza e di occupazione con cui mi sono scontrato, ma che sono riuscito a risolvere grazie soprattutto ad alcuni interessanti trucchetti che ho scoperto giocando direttamente con codice in bytecode (che ricordiamo e’ il codice intermedio in cui e’ rappresentata una classe java). Il risultato e’ una applet molto contenuta (poco piu’ di 40kb che diventano 20kb se la si comprime con il jar) in grado di caricare scene salvate in un formato compresso particolare (a3d) compatibile con tutti i browsers ma soprattutto in grado di aggiungere, con 50kb di spazio totale occupato (classi+scene+textures) ottimi effetti 3d a qualsiasi pagina web supportando svariate modalita’ di rendering e filtri di post-processing digitale. L’applet e’ visibile sul sito www.anfyjava.com sotto la voce anfy3d, ed e’ ovviamente scaricabile da chiunque gratuitamente. Come accennato prima, lo sviluppo di questa engine e’ stato molto utile per mettere a nudo la reale potenza di java, ma soprattutto per farmi un’idea dei sistemi di ottimizzazione adottabili su questa architettura. Ma a cosa puo’ servire ottimizzare una classe java ?!?Un esempio e’ proprio anfy3d, che dovendo rappresentare scene tridimensionali aggiornate in realtime, deve sottostare a certi vincoli temporali in modo da mostrare l’animazione in un modo ‘decentemente’ fluido.Ma l’ottimizzazione di una classe java e’ altresi’ importante sia per la realizzazione di sistemi embedded (che sono o meglio erano lo scopo reale del java), sia per interfacce di interazione con l’utente molto pesanti; inoltre una buona conoscenza del tipo di bytecode generato dal nostro sorgente java permette di ottenere classi piu’ compatte e piu’ efficienti. Le note che seguono, che indirizzano nella ricerca di vie piu’ brevi per scrivere un algoritmo, tentano di minimizzare lo scarto tra le varie JVM, cioe’ sono sistemi che danno miglioramenti di performance su tutte le implementazioni di una JVM, e non solo su una particolare; ovviamente esistono particolari ottimizzazioni specifiche per ciascuna implementazione e release della JVM, ma l’idea di fondo che mi ha spinto nella mia ricerca era quella di ottenere un buon bilanciamento globale delle prestazioni. Possiamo suddividere i tipi di ottimizzazioni possibili in 3 categorie:
Vincoli temporali intrinseci alla Java VMUno studio approfondito della Java VM e di come i vari compilatori java trasformano un sorgente in bytecode, permettono di ricavare le seguenti informazioni:-Se si ha a che fare con un loop molto pesante in termini di tempo, conviene osservare la presenza di eventuali termini costanti e tirarli fuori dal loop. Purtroppo i compilatori java attuali non fanno questa semplice ottimizzazione supportata invece dalla quasi totalita’ di compilatori C/C++ Es: for (int i=0; i<12;i++){risulta piu’ veloce se fatta cosi’: int d = a*b*c;Durante i calcoli conviene osservare la presenza di parti uguali in modo da non ripeterli. Ad esempio int e= a*b*(c*d/2); diventa:int f=c*d/2;
a[i] = a[i] + 4;e a[i] += 4;
Vincoli temporali dovuti ai metodi nativiPurtroppo per noi maniaci della velocita’, le classi native fornite dalla Sun o da altri produttori di JVM non sono assolutamente lo stato dell’arte dell’ottimizzazione ed il risultato e’ che spesso conviene riscriversi le funzioni critiche perche’ quelle fornite dal java, sebbene native, sono piu’ lente. Un esempio sono gli stream di dati, (DataInputStream e DataOutputStream), che sebbene una delle parti teoricamente piu’ utili, sono anche una delle parti piu’ lente di tutte.Un’altra parte notevolmente lenta e’ il supporto delle aree grafiche e delle funzioni matematiche: L’imageProducer si e’ rivelato essere una delle parti piu’ lente di tutte le librerie fornite con il java, ma purtroppo non puo’ essere scavalcato; in compenso, una parte che spesso puo’ essere critica, puo’ essere velocizzata con piccoli accorgimenti. Sto parlando delle funzioni matematiche del java, ed in particolare della libraria matematica del java, che gestisce unicamente dati di tipo double (i piu’ lenti in assoluto....). Se dobbiamo usare funzioni quali la radice etc, purtroppo abbiamo poco da fare; nel migliore dei casi conviene all’inizio del programma precalcolarsi una tabella gia’ convertita, magari, in float, da usare per i nostri usi: supponiamo ad esempio di avere a che fare con funzioni trigonometriche (come in una routine per ruotare punti) e immaginiamo di usare molto spesso la funzione Math.cos(angle); la prima cosa che si puo’ osservare e’ che tale funzione e’ periodica, e quindi in realta’ ci interessano solamente i valori di angle compresi nell’intervallo [0,2pi); a questo punto possiamo decidere di suddividere questo intervallo in una quantita’ fissata a priori (ad esempio 32768 valori) e creare un array di float contenente i giusti valori: float a[] = new float[32768];
int a = 1<<16;
Float c = b/65536.0;in modo da riconvertire in float (supposto che ci servano i float). Oppure utilizzare il risultato di tipo int con un semplice shift. Questo tipo di rappresentazione e’ molto utile anche per altri motivi: ad esempio e’ molto semplice fare il modulo di un numero (basta un masheramento con un and) ed implementare le funzioni di ceiling e di floor (molto utili nel campo della grafica tridimensionale in realtime). Un’altra classe fonte di lentezze inaspettate, e’ la classe String; di regola non andrebbe usata molto, visto che ogni operazione fatta su una String genera sempre nuovi oggetti, e solitamente si tende a fare molte operazioni sulle String, con conseguente generazione di migliaia di oggetti, spesso senza neanche accorgersene; se ad esempio ci servono molte concatenazioni e’ meglio utilizzare la classe StringBuffer. Infine lo spazzino..Il garbage collector fornito dal java e’ decisamente comodo e potente poiche’ ci libera completamente dalla necessita’ di tracciare la memoria disponibile, risolvendo molti problemi legati alla memoria che si incontrano quando si programma in C/C++ (e che sono fonte di innumerevoli bugs); ed e’ proprio questa una delle cause principali di rallentamenti quando abbiamo bisogno di codice veloce.. Purtroppo non e’ possibile determinare a priori quando il garbage collector entrera’ in azione, e se in quel momento stavamo disegnando un nuovo schermo, il risultato sara’ quello di una pausa (molto fastidiosa) tra un frame ed il successivo di una animazione. Ma questo e’ un problema che puo’ essere aggirato in un modo molto semplice: utilizzare il methodo gc() della classe System.In linea di massima bisogna operare in questo modo:
|
|
||
|
||
MokaByte ricerca
nuovi collaboratori
|
||
|