Ottimizzazione codice tra JVM diverse

Scritto da Michele Della Torre il 29 giugno 2008 – 20:53

Capita davvero di rado di avere problemi di prestazioni e quindi quando capita è un evento talmente eccezionale che merita uno studio approfondito.

Prima di entrare nei dettagli del codice e della soluzione, faccio una breve introduzione al problema.
L’applicazione in oggetto è un sistema di controllo domotico relativamente semplice, infatti il compito richiesto è il coordinamento della sola parte audio e video, senza altri tipi di integrazione.
Il controllo dei sistemi audio e video è un problema che ricorre in tutti gli impianti realizzati dall’azienda in cui lavoro attualmente, quindi per accelerare i tempi ho scritto un framework in Java in modo tale che tutte le problematiche ricorrenti vengano gestite nel modo più automatico possibile.; tale framework è abbastanza complesso perchè ogni impianto è fatto ad hoc per ogni cliente e quindi si ha una grande varietà di classi in grado di risolvere i problemi tipici.

Nell’ultima installazione, sebbene i singoli compiti fossero confinati al controllo dell’audio e del video, si aveva per la prima volta un insieme piuttosto vasto di apparecchi; in altri termini la singola stanza è semplice, ma quello che è elevato è il numero delle stanze stesse.
La centrale di controllo si interfaccia con i dispositivi o tramite una delle sue porte seriali oppure tramite una connessione IP.
Al momento del test dell’impianto è stato evidente che in alcuni casi le cose non andavano come previsto: la centrale di controllo inviava effettivamente i comandi, il dispositivo rispondeva in tempi brevissimi, ma l’applicativo di controllo vedeva la risposta solo dopo un po’, a volte il ritardo era anche di alcuni secondi.

Era necessario capire il problema e trovare la soluzione.
Avevamo due indizi fondamentali: il primo erano tutti gli impianti fatti in precedenza, anche più grandi di quello in oggetto e scritti nel linguaggio di programmazione proprietario della centrale, che hanno sempre funzionato senza problemi e che le connessioni seriali non davano alcun problema di sorta.
La conclusione era abbastanza evidente: per qualche motivo c’era un problema sulla ricezione di pacchetti IP dall’applicativo Java… ma dove?
Gli indagati erano 3:

  1. Il framework nella sua completezza
  2. la gestione delle connessioni IP a livello applicativo
  3. la JVM e il sistema operativo della centrale

Il framework nella sua completezza non sembrava il responsabile, visto che gestisce senza alcun rallentamento le connessione seriali, quindi il problema era o nelle connessioni IP a livello applicativo o in uno degli strati a cui si appoggia, ma sul campo non avevo modo di capirlo, infatti se la regola numero uno dell’ottimizzazione è

Non ottimizzare

la seconda è

Misurare, non tirare a indovinare

quindi sono tornato in ufficio e con calma mi sono messo a lavorare sulle connessioni IP a livello applicativo, che erano l’unica possibilità per me di migliorare la situazione: un rallentamento a livello di JVM o di sistema operativo infatti non sarebbero stato risolvibili da me direttamente.

L’ambiente di test che ho realizzato è stato molto semplice: due connessioni TCP/IP su socket locale che si scambiano dati secondo un protocollo banale, cioè un processo inviava dei dati all’altro che li rispediva indietro (in sostanza una delle due parti è un echo server).
Usando il profiler di Netbeans scopro che la maggior parte del tempo viene passata nella funzione di lettura dei dati da socket, questo mi ha dato due indicazioni importanti:

  1. il problema probabilmente era lì: il campo di ricerca si era ridotto
  2. la lettura però è bloccante, quindi è ragionevole che si passi molto tempo in quel metodo

Il metodo di lettura da socket è qualcosa di questo tipo:

public String read() throws IOException {

char[] buffer = new char[BUFFER_SIZE];

  int length = reader.read(buffer);

  // qualche check sui dati

  return new String(buffer, 0, length);
}


Il modo di procedere è stato analizzare i 4 blocchi lo compongono: ho cercato una soluzione più veloce per ogni parte confrontando i dati con quelli del caso base.
I check e la creazione della stringa in pratica non hanno impatto sulle prestazioni, così come la sostituzione del bufferedReader usato per incapsulare il socket con qualcosa di più leggero, mentre riciclando il buffer al posto di ricrearlo ad ogni invocazione le performance sono migliorate di un 40% abbondante, che è un ottimo incremento, superiore alle mie aspettative, infatti sarei stato pronto a scommettere che il collo di bottiglia fosse sulla lettura e non lì: ecco perchè è importante misurare sempre quando si ottimizza, lasciando in disparte il proprio intuito. 

Molto soddisfatto di quanto ottenuto, modifico anche il programma che sulla centrale e lo provo: con immensa sorpresa noto che l’incremento di prestazioni è qualcosa si eccezionale, oserei dire mostruoso. Giusto per dare qualche dato, sul mio pc si passa da 25 secondi a 14 per l’esecuzione di un certo numero di test, sulla centrale di controllo (con un numero di test diversi rispetto a quelli del pc, quindi i dati non sono confrontabili direttamente) da 209 secondi a 10!!! Un miglioramento di 20 volte è a dir poco incredibile.
La motivazione risiede nella JVM usata: su pc ho una Sun ufficiale ultima versione (1.6 con tutti gli aggiornamenti), mentre sulla centrale vi è una JVM non meglio definita capace di far girare J2ME (quindi 1.4).

Le conclusioni sono semplici:

  1. L’ottimizzazione fa effettuata solo quando vi è una forte evidenza della sua necessità
  2. Codice semplice, modulare e leggibile è da preferire sempre perchè rende più semplice l’eventuale sua ottimizzazione
  3. Non fidarsi mai del proprio intuito, è decisamente meglio affidarsi ad un profiler
  4. Se è stata effettuata una modifica che non ha apportato miglioramenti significativi, ritornare alla versione originale del codice se ne è stata compromessa la leggibilità
  5. Prestare grandissimi attenzione se si sta effettuando sviluppo multipiattaforma o cross-piattaforma: assicurarsi sempre che i miglioramenti siano evidenti anche per la piattaforma su cui si andrà in produzione: spesso le virtual machine e i compilatori sono in grado di aumentare le prestazioni molto più di quanto si creda
Posted under Informatica, Ingegneria del software | 1 Comment »

One Comment to “Ottimizzazione codice tra JVM diverse”


  1. Mauro Says:

    Bell’articolo Acce.
    Emblematico per far capire i concetti racchiusi nelle tue conclusioni, soprattutto i primi tre, secondo me.

    ciao ciao!