Qualche post fa abbiamo visto come ottimizzare le dimensioni di un file Javascript utilizzando strumenti tipo packer. Si ottiene quindi un file "minified", - ovvero minimizzato - come si dice in gergo.
Questa tecnica aiuta a diminuire i tempi di caricamento nel passaggio tra webserver e client.
Se pero' il nostro codice e' stato scritto "con i piedi", ovvero se non e' un codice ottimizzato sulla velocita' di esecuzione, trattandosi di un linguaggio interpretato in runtime, la nostra webapplication risultera' poco reattiva e impieghera' magari molto tempo per eseguire le istruzioni piu' onerose.
Tutto cio' ha la sua causa principale nel motore di parsing incluso nel browser. Quello che alcuni chiamano virtual machine di esecuzione del codice javascript.
Ci sono motori di parsing piu' efficienti e meno efficienti. E siccome i vari browser disponibili utilizzano praticamente motori di parsing diversi cio' porta a dire che ci sono browser piu' veloci e browser meno veloci ad eseguire una web application Ajax.
Nell'ambiente degli sviluppatori di applicazioni web c'e' la convinzione che il browser piu' lento di tutti sia Internet Explorer, mentre Firefox sia uno dei piu' veloci. E Safari sia il piu' veloce tra tutti.
In realta' la mia personale opinione e' che molto dipende dal codice javascript. I vari browser attuali di ultima generazione, tranne casi particolari, hanno ognuno i propri punti deboli. Ma sono tutti piu' o meno equivalenti. Tranne Internet Explorer 6 e 7 che hanno una grandissima inefficienza nella concatenazione e nel trattamento delle stringhe!
Come si puo' vedere in questa pagina link, sono stati effettuate delle misurazioni sui vari browser per catalogarne i punti deboli e i punti di forza di ognuno.
Con il benchmark utilizzato il browser piu' veloce risulta essere Opera9.5, ed il piu' lento IE7 (IE6 e' ancora piu' lento ma non e' stato considerato essendo della generazione precedente).
Si vede quindi che con un codice standard javascript usato come benchmark ogni browser ha delle debolezze relative. IE ha una debolezza assoluta eclatante.
Quindi ha senso in questi browser ottimizzare gli ambiti meno efficienti, per poter limare quanto piu' possibile ove il parser e' meno efficiente.
Prediamo ora in considerazione Internet Explorer (poiche' e' il browser piu' lento ed anche quello piu' diffuso) e vediamo alcune tecniche per migliorare le prestazioni di javascript.
Utilizziamo le variabili locali
Il parser quando esegue il nostro codice costruisce una "scope chain", ovvero una catena degli ambiti di validita' (o scope) delle variabili e delle funzioni/oggetti.
Una variabile definita dentro una funzione ha un ambito di validita' locale alla funzione. Fuori da quella funzione quella variabile non esiste, a meno che non ci sia una variabile definita estarnamente, che pero' e' effettivamente un oggetto diverso anche se avesse lo stesso nome della variabile definita internamente alla funzione utilizzando "var".
Potendoci essere funzioni annidate l'una dentro l'altra e' chiaro che viene definita una catena di scope per stabilire gli ambiti di validita' dei vari oggetti.
Ad esempio:
var a="variabile1";
function funzioneA(){
var b="variabile2";
var output=b+a;
return output;
}
alert(funzioneA());
Questa funzione utilizza una variabile locale - b - ed una variabile globale - a - per costruire una variabile di output locale con la concatenazione delle due stringhe iniziali. Il motore di parsing di IE risolve le variabili dallo scope piu' specifico a quello meno specifico (o piu' globale, se volete) quando deve eseguire le funzioni sulle variabili. Cosi' risulta dispendioso concatenare una variabile globale. Risulta piu' efficiente definire un reference locale che agisca da cache rispetto alla variabile globale (meglio eliminare le variabili globali del tutto!!!):
var a="variabile1";
function funzioneA(){
var b="variabile2";
var c=b; //meglio addirittura var a="variabile1"; localmente
var output=b+c;
return output;
}
alert(funzioneA());
Ottimizzazione del limite superiore dei cicli for()
Un altro aspetto ottimizzabile, che coinvolge questa volta i clicli for() in relazione agli array e' il seguente:
'var a=new Array();
....popoliamo l'array con N numeri...
//visualizziamo la somma di tutti i numeri contenuti
var output=0;
for(var idx=0;idx < a.length; idx++){ output += a[idx]; }
E' poco efficiente perche' ad ogni ciclo si valuta di nuovo la lunghezza dell'array!!!!
quindi si puo' ottimizzare in questo modo mettendo in cache locale la dimensione dell'array:
var len=a.length:
for(var i=0;i < len; i++} output+=a[i];
}
Ottimizzazione dei riferimenti impliciti
Supponiamo di avere una funzione che costruisce dinamicamente la nostra User Interface a blocchi - Titolo,Body e Footer - dentro un contenitore "baseElement" identificato dal suo ID:
function BuildUI(){
var baseElement = document.getElementById('target');
baseElement.innerHTML = ''; //Clear out the previous
baseElement.innerHTML += BuildTitle();
baseElement.innerHTML += BuildBody();
baseElement.innerHTML += BuildFooter();
}
In questo modo il nostro elemento target viene aggiornato piu' volte. Quando utilizziamo innerHTML il browser ricostruisce "al volo" il DOM e aggiorna il contenuto dell'elemento a video.
Quindi abbiamo una ripetizione di aggiornamenti.
Ottimizziamo il tutto effettuando un unica riscrittura del contenuto html del nostro elemento di base:
function BuildUI(){
var elementText=BuildTitle()+BuildBody()+BuildFooter();
document.getElementById('target').innerHTML = elementText;
}
Ottimizzazione dei rimandi impliciti ridondanti
Prendiamo in esame il codice seguente:
function CalculateSum(){
var addendoSinistra = document.body.all.addendoSinistra.value;
var addendoDestra = document.body.all.addendoDestra.value;
document.body.all.result.value = addendoSinistra + addendoDestra;
}
In questo modo andiamo a riconsiderare due volte la navigazione di tutti gli elementi del body del documento, per estrarre i due addendi della somma; Abbiamo quindi una ridondanza andando a referenziare di nuovo la collezione globale degli elementi della pagina.
Possiamo invece ottimizzare il tutto costruendo una prima collezione locale di elementi da utilizzare successivamente per estrare i valori degli addendi:
function CalculateSum(){
var collezione = document.body.all; //Cache this
var addendoSinistra= collezione.addendoSinistra.value;
var addendoDestra = collezione.addendoDestra.value;
collezione.result.value= addendoSinistra + addendoDestra;
}
Ricerca dei Simboli delle funzioni Javascript
Supponiamo di avere un ciclo for al cui interno richiamiamo una funzione JS:
'
for(var index=0;index < length;index++){ Work(myCollection[index]);
}
'
In questo modo il parser deve ricercare ad ogni passo per risolvere il puntatore-simbolo interno associato alla funzione richiamata all'interno della "scope chain".
Possiamo ottimizzare il codice creando una cache locale della funzione esterna. In questo modo il parser usera' la copia locale del puntatore-simbolo senza dover ripercorrere tutta la "scope chain":
'
var funcWork = Work;
for(var index = 0;index < length; index++){ funcWork(myCollection[index]);
}
'
Allo stesso modo questo discorso vale per le funzioni del DOM interno ad IE:
'
var parentElement=document.getElementById('target');
var length = myCollection.getItemCount();
for(var index = 0; index.length; index++){
parentElement.appendChild(myCollection[iterate]);
}
'
diventa:
'
var funcAppendChild = document.getElementById('target').appendChild;
var length = myCollection.getItemCount();
for(var index = 0; index.length; index++){
funcAppendChild(myCollection[iterate]);
}
'
Concatenazione di stringhe
Veniamo ora alle dolenti note da cui abbiamo iniziato il nostro discorso: la concatenazione delle stringhe sotto Internet Explorer utilizzando l'operatore di concatenazione "+=" su oggetti di tipo stringa.
Benche' il parser javascript sia stato migliorato tantissimo nella gestione della concatenazione delle stringhe nel passaggio dalla versione 6 alla versione 7 del browser, rimane ancora l'operazione piu' lenta in assoluto rispetto a qualunque altro browser.
L'inefficenza di questa operazione e' tale che si puo' avere un rallentamento tangibile della nostra web application quando debba eseguire la concatenazione di molte stringhe (diciamo almeno una decina).
Come possiamo ottimizzare questa operazione?
Usando il metodo join('') dell'oggetto Array in cui metteremo le stringhe da concatenare:
var smallerStrings = new Array();
var destLargeString="";
smallerStrings.push(""stringa1");
smallerStrings.push(""stringa2");
smallerStrings.push(""stringa3");
smallerStrings.push(""stringa4");
...
smallerStrings.push(""stringaN");
var destLargeString = smallerStrings.join('');
In questo modo sfruttiamo un metodo interno al browser piu' efficiente, migliorando abbastanza il punto piu' critico del browser IE.
Riassunto
In questo post ho voluto mostrare alcune delle tecniche piu' conosciute di ottimizzazione del codice javascript sotto Internet Explorer che utilizzo piu' frequentemente e che effettivamente danno una buona ottimizzazione dei punti critici in questo browser. In realta' ci sono anche altre tecniche avanzate, tipo i cicli do-while inversi autoestinguenti e l' "unfolding" parziale dei cicli for valide in generale ma che non e' detto che diano dappertutto dei reali vantaggi.
Ritengo che solitamente sia sufficiente questo primo ciclo di ottimizzazione del codice, perche' se non si avesse comunque una web application reattiva probabilmente il problema e' piu' generale a livello dell'architettura complessiva, e non ristretto alla velocita' di parsing del nostro codice javascript.
Come ultimo consiglio suggerisco comunque di misurare i tempi di esecuzione dei punti che vogliamo ottimizzare per verificare direttamente quanto tempo si risparmia, e per non perdere magari troppo tempo a rivisitare il nostro codice per guadagnarci solo pochi millisecondi.
Per chi avesse voglia di approfondire ulteriormente l'argomento puo' seguire questa presentazione fatta dal gruppo di sviluppo di Internet Explorer
venerdì 18 gennaio 2008
Ottimizzazione del codice Javascript
Pubblicato da
Massimiliano Modena
alle
10:19
Iscriviti a:
Commenti sul post (Atom)
2 commenti:
c'è un errore in uno degli esempi mi pare:
var len=a.length:
for(var i=0;i < length; i++} output+=a[i];
}
deve essere scritto:
var len=a.length:
for(var i=0;i < len; i++} output+=a[i];
}
Grazie per la segnalazione. provvedo a sistemarlo.
M.
Posta un commento