Archivi tag: prestazioni

Profilare con Xcode, alla ricerca di memory leaks e non solo

Chi fa il nostro mestiere prima o poi deve affrontare dei problemi di memoria… che la si perda a causa di stress e del passare degli anni, o si scriva programmi che si affidano ad allocazione/deallocazione automatica, prima o poi tutti abbiamo problemi con questo prezioso strumento.

Il caso peggiore: memory leak in applicazione web Java

Tempo fa lavorando ad un grosso progetto web sviluppato in Java (JSF2) ci ritrovammo a metterci le mani nei capelli a causa di qualche memory leaks che rendevano per così dire “poco scalabile” la nostra applicazione. Allora usammo l’Eclipse Memory Analyzer MAT, strumento molto potente e ben fatto che permette di analizzare lo heap di qualunque applicazione Java.
Su un’applicazione web certi problemi possono essere devastanti, mentre in programmi che girano localmente su macchine provviste di parecchia RAM spesso nemmeno ci si rende conto che il nostro programma sta lentamente mangiando tutta la memoria senza rilasciarla, finché va in crash. Se però questa cosa succede di rado, e in quelle poche occasioni stavamo facendo operazioni parecchio onerose, spesso nemmeno ci facciamo caso e/o facciamo finta di niente.
Memory Leak - Comic

Memory leak?

Se non siete uno degli ultimi due personaggi della vignetta dovreste sapere a grandi linee di che si tratta, ma nel dubbio cerco di spiegarlo in poche parole: un programmatore ha sbagliato qualcosa e tra le righe di codice c’è qualche operazione che occupa memoria senza rilasciarla al termine.
Se non si programma in C raramente ci si deve preoccupare di allocare e deallocare la memoria manualmente, perché quasi tutti i linguaggi gestiscono la memoria autonomamente, allocandola ad ogni nostra dichiarazione di variabile, e facendola liberare da un garbage collector. Il GC è un processo demone che scansiona l’area di memoria dove risiedono gli oggetti e le variabili creati dal nostro programma ed elimina quelli rimasti per così dire “isolati”, ovvero che non sono più referenziati da nessuno o che sono collegati solo a oggetti non referenziati da parti “vive” dell’applicazione. In presenza di GC i memory leak sono rari perché il GC a differenza nostra non si dimentica di liberare la memoria, ma a volte proprio non ce la fa a causa di strutture mal progettate che generano riferimenti “eterni” a variabili che credevamo temporanee, e in questi casi sono dolori.

Tipico grafico di un'applicazione affetta da memory leaks, lo heap si riempe e boom

Tipico grafico di un’applicazione affetta da memory leaks

Analisi della memoria su Xcode

Sviluppando in nativo su iOS non so quanto spesso capiti di dover profilare l’applicazione alla ricerca di problemi di memoria, ma parlando di Titanium io personalmente ho dovuto preoccuparmene in più di un’occasione. Il Mac è un prodotto del demonio, però Xcode fornisce alcuni validi strumenti che in questi frangenti tornano molto utili. Vediamo come profilare un’applicazione alla ricerca di problemi di memoria e di prestazioni.

Aprendo un progetto Xcode ci si trova di fronte la finestra delle impostazioni generali, e qui dobbiamo definire le impostazioni di deploy. Generalmente la versione di iOS e il tipo di dispositivo.
IOS Profiling 1
Scegliamo la destinazione del nostro test, tra eventuali dispositivi collegati al Mac e simulatori del tipo selezionato al passo precedente. Fatto questo dobbiamo ricompilare il progetto. Considerato che stiamo per fare una profilazione, tanto vale fare il “Build for > profiling”, e al termine avviamo la profilazione con “Profile”.
IOS Profiling 2
Selezioniamo il tipo di template che più si adatta alle nostre esigenze o creiamo un template contenente tutte le metriche che ci interessano.
IOS Profiling 5
Nella schermata che si apre, quando siamo pronti, premiamo il tasto di registrazione e l’applicazione inizia a girare mandando dati agli strumenti di diagnostica scelti.
In cima all’elenco possiamo leggere istante per istante i dati generali i utilizzo della memoria, e subito sotto quali sono i tipi di oggetti che contribuiscono in maggior misura a saturare la nostra cara RAM. Il diagramma parla più di tutti, se questo inizia lentamente ad assomigliare ad una sega inclinata verso l’alto sono cazz… problemi.
IOS Profiling 6
Se certe parti del flusso del programma sono sospette e volete vedere in dettaglio come cambia l’utilizzo della memoria facendo una determinata operazione non serve segnarsi i numeri sul foglio ma possiamo usare l’utilissimo pulsante “Mark generation”. Questo serve a scattare un’istantanea di un certo punto, istantanee che possono poi essere confrontate per vedere come cambia l’utilizzo della memoria tra queste diverse “generazioni” (e quali oggetti rimangono in memoria).
Nel mio caso la memoria rimane stabile e decresce alla chiusura di alcune finestre parecchio pesanti. Se in certi punti pensate ci siano dei memory leak ma i “buchi sul tubo” sono troppo piccoli da rilevare potete prendere un trapano e allargare il buco… basta aggiungere delle immagini o in generale blob pesanti in variabili sospette per vedere meglio la RAM che se ne va.
IOS Profiling 7

Sviluppo per iOS su Titanium

Se stiamo sviluppando con Titanium probabilmente Xcode lo conosciamo molto poco, eppure nella vostra cartella del progetto (sotto build/iphone) ci dovrebbe essere un file .xcodeproj. Apritelo e vi ritroverete con il progetto Xcode che potete maneggiare e lanciare a piacimento

La cartella del progetto iOS generato da Titanium

La cartella del progetto iOS generato da Titanium

Senza scomodare Xcode

Certi strumenti sono molto potenti e possono aiutarvi molto, ma se siete dei romantici e preferite migliorare le prestazioni del vostro programma basandovi solo sull’intuito potete comunque usare la finestra di Monitoraggio delle attività di OSX. Ogni programma lanciato nel simulatore iPad/iPhone è un processo a se stante, e potete quindi tenere sempre sott’occhio la memoria che questo utilizza.

Monitorare il processo spesso è sufficiente

Monitorare il processo spesso è sufficiente

Intuito, Xcode, oscilloscopio, qualunque mezzo decidiate di usare l’importante è che ogni tanto verifichiate la stabilità e la non eccessiva onerosità dei vostri programmi. Alcuni utenti non sono molto comprensivi – giustamente – quando l’applicazione si rallenta in modo esagerato con il passare del tempo o peggio scompare dalla loro vista all’improvviso.

Memoization: metti il turbo alle tue funzioni Javascript

La memoizzazione – da non confondere con memorizzazione – è una tecnica usata allo scopo di evitare che una stessa funzione, onerosa dal punto di vista computazionale, venga eseguita più volte con gli stessi valori. In pratica si fa in modo che il valore di ritorno corrispondente a certi parametri finisca in una sorta di cache, cosicché eventuali successive chiamate alla stessa funzione con gli stessi parametri non comportino delle nuove esecuzioni della funzione stessa.
In javascript questo simpatico giochetto si può implementare in un paio di modi, a seconda che la funzione accetti parametri oppure no.

Funzione senza parametri, caso più “semplice”

Ipotizziamo che nel nostro programma da qualche parte ci sia una funzione che non si aspetti alcun parametro in ingresso e che durante l’esecuzione del programma potrebbe dover essere eseguita più volte. In questo caso – non molto frequente ma nemmeno raro – mettere il risultato in una “cache” non ha proprio senso perché non si ha nemmeno una “chiave” con cui riandarlo a pescare. Si possono quindi sfruttare gli scope e il fatto che una funzione è un oggetto come tutti gli altri (e quindi memorizzabile in una variabile).

// funzione originale
var getSomething = function(){
  var something;
  something = {}; // logica onerosa
  return something;
};

// funzione modificata
var getSomethingMem = function(){
  var something;

  something = {}; // logica onerosa

  getSomethingMem = function(){
    return something;
  };
  return getSomethingMem();
};

Si ha una funzione anonima getSomething che esegue tutta una serie di calcoli onerosi e ritorna un risultato. Per farne il memoize è sufficiente – una volta eseguita la computazione e salvato il risultato in una variabile – assegnare alla variabile contenente questa funzione una nuova funzione che ritorni direttamente il valore calcolato. La variabile something è inclusa nello scope di questa nuova funzione “fasulla” getSomethingMem, e questa funzione dopo la prima esecuzione non farà altro che ritornare il valore calcolato la prima volta.

Funzione con parametri, la cache

Il “trucco” illustrato sopra non si può applicare al caso di funzione che si aspetta dei parametri in ingresso, ma la flessibilità del javascript ci permette di assegnare una cache a qualunque funzione.
Spiegarla a parole non è proprio semplice lascio quindi a voi il piacere di capirla, in fondo sono solo poche righe di codice.
In questo caso la “cache” è un modulo CommonJs, la sua funzione create prende in ingresso una funzione (ad esempio una list) e ritorna un oggetto avente una sua implementazione “estesa” con la cache della stessa funzione list, più eventuali altre funzioni di utilità.

var list = function(params){
  // logica onerosa, ma stateless e indipendente dal tempo
  return {};
};

list = require('Cache').create(list).get;
exports.create = function(f, params){
  params = params || {};
  
  var cache, get, reset;
  
  cache = {};
  
  get = function(){
    var key = JSON.stringify(Array.prototype.slice.call(arguments));
    if (key in cache){
      console.log('From cache: ' + key);
      return cache[key];
    } else {
      console.log('Not in cache: ' + key);
      return cache[key] = f.apply(this, arguments);
    }
  };
  
  reset = function(){
    cache = {};
  };
  
  return {
    'get' : get,
    'reset' : reset
  };
};

Detto che la funzione di memoize può essere implementata in vari modi (quella sopra è una versione modificata di una che trovai da qualche parte sul web), è chiaro che si possono trovare librerie che ci alleggeriscono del compito. Librerie -> Javascript -> ovviamente qualcosa c’è anche in Underscore.js.

In sintesi

È la memoization la cura per ogni male? Chiaramente no. Può essere usata sempre? No, la risposta è quasi sempre no quando in una domanda compaiono dei quantificatori universali come “ogni” o “sempre”…
Queste tecniche potrebbero risolvervi grossi problemi di prestazioni ad esempio in presenza di funzioni lente perché causa di molte letture su disco, a discapito di un maggior utilizzo della memoria, ma questo divario si riduce di molto se i dischi sono SSD.
In presenza di funzioni stateful (il male, almeno quando si usano linguaggi funzionali), chiaramente non si può pensare di bypassare l’esecuzione della funzione andando a pescare il valore in una cache, così come non si può fare troppo affidamento su una cache se i risultati cambiano allo scorrere del tempo (ad esempio perché le elaborazioni sono basate su dati aggiornati periodicamente).
Il caso limite è quello di una funzione molto complessa che fa molte query su un database i cui dati cambiano ma non troppo spesso. In un caso del genere il suo utilizzo ci potrebbe stare ma bisognerebbe implementare adeguate logiche di “pulizia” della cache dei risultati a ogni aggiornamento dei dati.

A usarla bene il vostro smartphone/tablet potrebbe anche spiccare il volo, ma facendolo con leggerezza i risultati delle vostre funzioni saranno sbagliati… “Da un grande potere derivano grandi responsabilità”.

Concatenare stringhe in Javascript con la funzione join

La concatenazione di stringhe per molti potrà sembrare un argomento di poco conto… e infatti lo è anche per me. Ciononostante vorrei affrontare l’argomento perché c’è una tecnica che aiuta a mantenere il codice pulito ed è anche efficiente.
In Java per ottimizzare l’utilizzo delle memoria quando si lavora con molte stringhe c’è la classe StringBuilder, qui non abbiamo classi particolari ma la funzione join dell’array produce un risultato molto simile allo StringBuilder.

Vediamo prima un classico esempio di concatenazione di stringhe con carattere separatore tra una e l’altra.

var arr, i, sum, beginTime;
 
arr = new Array(100000);
for (i=arr.length-1;i>0;i--){ arr[i] = ""+i; }
 
// first
sum = "";
beginTime = +new Date();
for (i in arr){
  if (sum){ sum += ","; }
  sum += "" + arr[i];
}
console.log(new Date() - beginTime);

// second
sum = "";
beginTime = +new Date();
for (i in arr){ sum += "" + arr[i]; }
sum = sum.slice(0, sum.length - 1);
console.log(new Date() - beginTime);

// third
sum = "";
beginTime = +new Date();
sum = arr.join(",");
console.log(new Date() - beginTime);

I primi due snippet utilizzano la concatenazione classica con +, ma mentre nel primo caso il carattere separatore viene inserito con un’istruzione condizionale, nel secondo viene inserito sempre rimuovendo il carattere eccedente aggiunto in fondo. Ebbene la tecnica dello slice è sempre più prestante, e il codice più bello.
Il terzo esempio utilizza invece la funzione join degli Array, come promesso nel titolo. In questo caso il codice è estremamente più pulito, perché si prende l’array così com’è e si specifica (l’eventuale) carattere separatore. Il bello è che non si guadagna soltanto in leggibilità e manutenibilità, ma anche la velocità è notevolmente più alta, specialmente su SpiderMonkey.

In altri casi capita di dover comporre delle stringhe avendone dei pezzi, caso tipico una query sql. Lasciamo stare per il momento le sql injection supponendo che i parametri siano opportunamente parsati e la query utilizzi dei placeholder.

var sum =
  " SELECT ... " +
  " FROM ... " +
  " JOIN ... " +
  " JOIN ... " +
  " WHERE ... " +
  " ORDER BY ...";

sum = [
  " SELECT ... ",
  " FROM ... ",
  " JOIN ... ",
  " JOIN ... ",
  " WHERE ... ",
  " ORDER BY ..."
].join();

I volumi in gioco sono così insignificanti che c’è da vergognarsi a confrontarne le prestazioni, e in questo caso specifico la scelta della concatenazione tradizionale o di quella con join è più un fatto di gusti. Resta il fatto che a me la seconda forma piace di più, anche perché si ha sempre il vantaggio di poter specificare un carattere separatore da usare sempre tra un pezzo e l’altro.

Conclusioni

Inutile dilungarcisi troppo: cercate di usare sempre il join quando dovete concatenare delle stringhe contenute in un array, e se non è questo il vostro caso forse è il caso di farcelo diventare comunque, perché tutti quei + in giro per il codice non sono il massimo e ricondurre “gruppi” di stringhe ad array può essere comunque una buona idea.

Cicli in Javascript, ovvero non badate alle performance che è tempo sprecato

Qualche tempo fa avevo seguito un corso online di Javascript, recente e ben fatto, tanto per verificare che le mie conoscenze dello strumento che uso tutti i giorni fossero corrette e aggiornate. Una delle puntate riguardava le performance, e poneva particolare enfasi sull’importanza di scrivere bene i cicli per far evitare all’interprete letture inutili e ottimizzarne così le prestazioni, e con mia grande gioia ho appurato di essere allineato al “professore”. Poi però mi sono ricordato di come un mio amico molto esperto mi disse che secondo lui tutte queste considerazioni sono più dannose che inutili grazie all’efficienza degli ultimi interpreti Javascript, V8 e SpiderMonkey su tutti.

Forse qualcuno potrebbe considerare anacronistico porsi questi problemi nel 2015, specialmente parlando di un linguaggio usato generalmente ad alto livello, ma per me quando si scrive codice bisogna sempre cercare di farlo nel modo migliore, quindi ecco un semplice test.

var arr, i, sum, beginTime, iLimit;

arr = new Array(100000);
for (i=arr.length-1;i>0;i--){ arr[i] = i; }

// first
sum = 0;
beginTime = +new Date();
for (i in arr){ sum += arr[i]; }
console.log(new Date() - beginTime);

// second
sum = 0;
beginTime = +new Date();
for (i=0;i<arr.length;i++){ sum += arr[i]; }
console.log(new Date() - beginTime);

// third
sum = 0;
beginTime = +new Date();
for (i=0, iLimit=arr.length;i<iLimit;i++){ sum += arr[i]; }
console.log(new Date() - beginTime);

// fourth
sum = 0;
beginTime = +new Date();
for (i=arr.length-1;i>0;i--){ sum += arr[i]; }
console.log(new Date() - beginTime);

Sarei tentato di lasciarvi senza responso per farvi copiare e incollare questo codice sulle console di Chrome e Firefox, ma lungi da me essere crudele vi anticipo il verdetto (che potete immaginare leggendo il titolo dell’articolo): scriveteli come vi rimane più comodo.
Le due forme centrali, cioè il ciclo for normale “dall’inizio alla fine” e quello “dall’inizio alla fine con memorizzazione della dimensione”, sono su entrambe le console i più lenti. Ma mentre per il secondo esempio ce lo si poteva aspettare così non è per il terzo, che a detta del “professore” dovrebbe essere quanto meno più veloce del secondo, eppure così non è.

Forse non è corretto includere in questo test il for-in del primo esempio, perché ha una semantica e un utilizzo diversi rispetto agli altri, ma nonostante la sua maggior versatilità per V8 è proprio questo il vincitore, mentre per SpiderMonkey è il “ciclo for a rovescio” a vincere.

Conclusioni

In sostanza se non sapete dove sarà eseguito il vostro codice non avete elementi per stabilire quale forma è migliore, e anche se lo sapeste tutto sarebbe vanificato dal progresso dagli avanzatissimi algoritmi di calcolo messi in campo da Google e Mozilla. Quindi lasciate perdere e scrivete i vostri cicli come vi rimane più comodo, possibilmente a rovescio se il vostro algoritmo ve lo consente, ma evitando inutili complicanze come quella di salvarsi la dimensione dell’array… che io faccio sempre.