martedì 6 novembre 2007

Come migliorare le prestazioni di una Rich Internet Application Ajax minimizzando i caricamenti dei files JavaScript e CSS

Tutti quelli che hanno sviluppato software sanno che le performances di un programma contano tantissimo, e che un codice scritto senza tenere conto delle sue prestazioni in esecuzione puo' generare un programma lento e quindi poco efficiente.

Che si fa nei casi in cui si scopre che un codice "gira" lento? Per prima cosa si cercano i punti critici di inefficienza e quindi si cerca di intervenire utilizzando algoritmi piu' efficienti per migliorare le prestazioni della porzione di codice causa di inefficienza.

Per far cio' viene in aiuto tutta la Teoria della complessità computazionale, che misura l'efficienza degli algoritmi. Di sicuro un algoritmo di ordinamento che in un numero di passi finito possa trattare infiniti dati sarebbe il non-plus-ultra per un progettista di software. Ma sappiamo anche che un algoritmo con complessita' O(log n) e' piu' efficiente di un algoritmo con complessita' O(n).

Ma come interviene questo discorso nelle nostre Rich Internet Applications sviluppate col paradigma Ajax?

Innanzitutto occorre dire quali possono essere i punti piu' critici nello sviluppo di una RIA Ajax per quanto riguarda le performance della nostra applicazione: Javascript senza dubbio.

Non per colpa sua, secondo me, ma per colpa del browser su cui deve essere interpretato ed eseguito.

Si, certo, e' un linguaggio interpretato in run-time, e come tale non il massimo della vita in termini di efficienza in fase di esecuzione. Credetemi se non l'avete mai provato, ma ad esempio anche Flex usa un linguaggio , l' ActionScript, che e' cugino di Javascript perche' aderisce alla medesima standardizzazione, l'ECMAScript. Ed anche ActionScript viene interpretato dalla propria Virtual Machine.

Ma una applicazione sviluppata in Flex e' molto piu' veloce di una RIA Ajax in Javascript.

Perche' la macchina virtuale che interpreta il codice Javascript all'interno dei vari browser non e' mai stata ottimizzata come si deve, secondo me.

Ad esempio, utilizzando Internet Explorer, se si esegue una procedura Javascript il browser facilmente utilizzera' tutte le risorse a disposizione in termini di percentuale di CPU e anche la piu' semplice delle operazioni eseguita su una macchina non aggiornatissima in termini di CPU, se non ottimizzata, puo' portare a tempi di esecuzione di qualche secondo.
E qualche secondo con la CPU al 100% puo' far innervosire l'utente finale che nel frattempo difficilmente potra' fare qualcos'altro se non attendere che il browser abbia terminato.

I 4 principale browser utilizzati oggigiorno, IE 7, Firefox 2, Safari 3 ed Opera 9 hanno fatto comunque importanti passi in avanti riguardo alle performances con Javascript rispetto alle versioni precedenti.
Ed in particolare Safari 3 ha raggiunto un ottimo livello di velocita'. Ma ancora non basta.

Quindi nelle nostre applicazioni web Ajax assume grande importanza avere un codice Javascript un minimo ottimizzato. Cioe' non scritto con i piedi, per intenderci...

Questo per quanto riguarda aspetti comuni anche con tutti gli altri linguaggi di programmazione.

Pero' c'e' un aspetto peculiare delle web applications che assume ancor piu' rilevanza in Ajax: i tempi di caricamento delle pagine web.

Se assimiliamo la nostra web application complessa ad una pagina web, si hanno piu' fasi da considerare quando un utente vuole utilizzare questa applicazione sul suo browser.

  • La prima, se la nostra pagina e' dinamica, e' data dal tempo impiegato dal server a creare l'output html da inviare al client.
  • La seconda fase e' quella del caricamento della pagina dal web server al client.
  • Poi interviene il parsing del codice JavaScript e la costruzione del Document Object Model sul client.
  • Infine c'e' una fase di rendering degli elementi che si vogliono visualizzare sullo schermo.
A questo proposito si puo' vedere il link seguente per vedere una applicazione di benchmark che mette a confronto diverse tecnologie per implementare Rich Internet Applications riguardo le varie fasi di caricamento.

Riguardo al primo punto non sempre avviene, se ad esempio costruiamo la nostra web application con una pagina introduttiva snella e soprattutto statica.

E' la seconda fase a darci i problemi maggiori di solito. Usando il paradigma Ajax si ha a che fare ora con un sistema di gestione della nostra applicazione scritto in Javascript che deve essere eseguito ora sul client.
Chiamero' questo sistema di gestione "engine", o motore, della nostra applicazione.

L'engine Ajax si deve occupare delle richieste asincrone da inviare al server usando l'oggetto XMLHttpRequest, della distribuzione dei dati risultanti ricevuti dal serve negli elementi della nostra applicazione, e dell'interazione con l'utente.

Facilmente si deve implementare un engine complesso qualche migliaio di linee di codice e grande qualche centinaio di KB. Commenti nel codice a parte.

E la criticità' di questa fase e' dovuta alla banda disponibile nel collegamento tra client e server.

Se dobbiamo caricare diverse centinaia di kiloBytes con un modem a 56Kbit dovremo mettere in conto anche qualche decina di secondi di attesa.

Se poi dobbiamo caricare piu' files Javascript il browser per ognuno di essi deve aprire una sessione di caricamento, aumentando cosi' il tempo totale impiegato. Stesso discorso per tutti gli altri files eventualmente da caricare, come possono essere i files CSS.

Quindi alla fine dei conti non e' infrequente trovarsi a dover attendere qualche secondo di caricamento iniziale anche disponendo di un collegamento a banda larga verso il web server.

Come si puo' operare allora per migliorare le performances di caricamento iniziale?

Innanzitutto diminuendo il numero di files accessori che si cerca di caricare. Cioe' se abbiamo per ipotesi 5 CSS e 5 Javascript, proviamo a mettere tutto il codice CSS in un singolo file per il fogli di stile e tutto il codice Javascript in un unico file per lo script lato client.

Poi sta alla sensibilita' di ogni progettista mediare tra la necessita' di diminuire le richieste verso il server e mantenere comunque una discreta manutenibilita' dei vari files di scripting, per cui magari si preferisce tenere separate parti di codice in unita' logiche distinte.

Secondo punto su cui e' possibile intervenire e' comprimere il codice Javascript.

Teniamo presente che, trattandosi di un linguaggio interpretato, i commenti vengono ignorati dal motore del browser in fase di parsing.

Quindi potremmo creare una versione dell'engine Ajax di debug o di sviluppo, con tutti i commenti presenti, e poi una versione di "produzione" in cui si e' fatto lo "stripping" dei commenti, degli spazi in eccesso, dei ritorni di riga, e di tutti quegli elementi ignorati in fase di parsing del codice.

Gia' cosi' un listato Javascript, con commenti ridotti all'osso puo' "dimagrire" dal 10% al 30%. Si dice quindi che un file viene "minimizzato".

Poi un file javascript puo' anche venire compresso.

In un codice Javascript a parte le parole chiave proprie del linguaggio, compaiono sostanzialmente nomi di variabili/oggetti e nomi di funzione. Trovare un oggetto "itemImageArray" o delle funzioni "setControlText" o "disp_controlUserCountryEditing" e' la normalita'. Di solito tendiamo a scrivere codice criptico per gli altri, ma per noi che lo scriviamo comunque ha un suo senso, una sua logica per i nomi che abbiamo utilizzato al suo interno.

Di certo e' raro che scriviamo un codice con oggetti denominati _a, xy, s3 oppure funzioni g6( ), 4h( ), qw( ). Senno' una volta scritto il nostro programma diventa incomprensibile anche a noi stessi che lo abbiamo pensato!!!

Esistono quindi strumenti tipo packer che effettuano questa trasformazione dei nomi di variabile e di funzione, costruendo uno spazio dei nomi abbreviati - incomprensibili per una persona - e comprimendo di fatto le dimensioni del file javascript originale anche fino all'80%!!!!

Capita quindi che da un file di partenza di 100KB o piu' si riesca ad ottenere un file di 10KB o meno. E su una connessione a banda stretta la differenza nei tempi di caricamento e' notevole.

Uno svantaggio dell'utilizzo di uno strumento come packer e' che se manca un ";" come chiusura di una funzione anonima, il parser javascript non riesce piu' ad interpretare il file, mentre normalmente cio' non accade con la versione non minimizzata e/o compressa anche se previsto dal linguaggio.

Vabbe', meglio direi io, cosi' si e' obbligati a scrivere codice migliore e piu' robusto, dovendo seguire per forza le regole formali del linguaggio.

Un ulteriore vantaggio e' ottenere un minimo grado di "Obfuscation".

Siccome Javascript richiede un sorgente da interpretare, chiunque puo' scaricare un engine Ajax e fare "retro-engineering" del codice e sfruttare il lavoro fatto da altri. Utilizzando packer si rende il codice incomprensibile ad una lettura superficiale. In realta' e' possibile risalire al codice originale compresso, con strumenti come "unpacker".

Se si volesse rendere ancora piu' difficile il la retroingegnerizzazione del codice occorrerebbe usare strumenti piu' avanzati che non permetterebbero la decompressione del codice una volta processato.

Ma cio' trascende le mie conoscenze e quindi lo spirito di questi post.

Un ultimo accenno ancora: su Internet Infomation Services 6.0 (IIS) il web server Microsoft presente su WindowsXP Server 2003, e mi pare anche su Windows Vista, esiste la possibilita' di attivare una opzione per gzippare lo stream di dati inviato dal web server ai vari client.

Chi di voi non ha mai zippato un file? Ecco, gzip e' uno dei tanti programmi per comprimre un file, compatibile con il formato di compressione ZIP. E i browser recenti hanno la possibilita' di ricevere uno stream di dati compresso con gzip effettuandone la decompressione "al volo" una volta ricevuti i file compressi.

IIS 6.0 si occupa quindi della compressione in automatico di un file javascript, html o css e dell'invio al client del file in versione compressa. Poi ci pensa il browser a decomprimerlo e ad utilizzare correttamente il file originale.

Personalmente non ho mai avuto occasione di provare la compressione HTTP diretta anche se mi aspetto comunque un ulteriore dimagrimento dell'Ajax engine da caricare, magari minimo, a fronte di una latenza aggiuntiva dovuta alla decompressione sul client del file zippato.

Occorrerebbe quindi verificare di caso in caso se e' maggiore il risparmio dovuto al dimagrimento del file o la latenza aggiuntiva. Se non si utilizza packer pero' e' sicuramente maggiore il vantaggio dato dalla compressione del file con gzip rispetto alla latenza per la sua decompressione, soprattutto con un hardware del client di ultima generazione.

Chi volesse puo' leggere l'articolo seguente che spiega come attivare la compressione HTTP: Using HTTP compression with IIS 6.0. So che anche con Apache e' possibile effettuare la compressione HTTP diretta, e doverbbe essere facile reperire informazioni tecniche a riguardo su www.apache.org sui moduli mod_gzip e mod_ziplook e mod_deflate.

Nessun commento: