Ottimizziamo il nostro codice MATLAB!

Il calcolo parallelo è stato una delle maggiori rivoluzioni nel mondo della programmazione, permettendo agli sviluppatori di andare oltre i limiti imposti dalle tecniche classiche. L'idea è semplice, quasi un proverbiale 'uovo di Colombo': dato che vi è un limite, di natura fisica, alla velocità con cui una sequenza di singole istruzioni può essere eseguita, perché non distribuire il carico computazionale tra più agenti, permettendo l'esecuzione contemporanea di più istruzioni? Si è quindi passati ad architetture ad alte prestazioni, che possono gestire carichi computazionali molto elevati (come, ad esempio, nel deep learning), garantendo performance ottimali in ogni situazione. Anche, e soprattutto, in presenza di codice che, in ingegneria del software, sarebbe classificato come 'technical debt': in parole povere, non ottimizzato. Ed è questo il problema che affronteremo, dimostrando quanto possano essere significativi i miglioramenti legati all'ottimizzazione attraverso il nostro fedele MATLAB.

Introduzione

Questo articolo è prevalentemente incentrato sull'ottimizzazione del codice in MATLAB, ma i concetti che esporremo possono essere 'traslati' verso qualsiasi altro linguaggio o ambiente di programmazione. Ad ogni modo, vi consiglio un paio di tutorial, che vi esporranno i concetti introduttivi, o alcuni costrutti un po' più avanzati, grazie ai quali potrete seguire agevolmente l'articolo.

L'ovvia tentazione, quando si tenta di ottimizzare le performance dei nostri programmi, è la ricerca di tecniche che permettano, tramite modifiche minime (e, soprattutto, lasciando intonsi i nostri preziosi algoritmi e strutture dati), di ottenere i risultati sperati. Purtroppo, però, il miglioramento delle prestazioni richiede la conoscenza approfondita sia del nostro algoritmo che del modo in cui lavora il compilatore (o l'interprete) che stiamo usando: questo è tanto più vero quanto più siamo ad alto livello, in quanto istruzioni più human-friendly significano maggiore astrazione, e quindi maggiore onere necessario per la conversione a basso livello. Qual è la soluzione? Abbiamo nominato, nel cappello introduttivo, il calcolo parallelo (nel seguente link trovate articoli a riguardo), ma possiamo anche citare il GPU computing: soluzioni di questo tipo, che di fatto rappresentano una sorta di 'shortcut', non sono sempre efficaci, o utilizzabili. Facciamo un esempio banale: supponiamo di voler scrivere un programma che valuti l'andamento del nostro conto corrente nei prossimi n anni, ovviamente con alcune ipotesi semplificative (tasso di credito costante, assenza di inflazione, prelievi o versamenti). In questo caso, è banale appurare che il saldo del conto all'anno i dipenderà dal saldo all'anno (i - 1), il quale dipenderà a sua volta dal saldo all'anno (i - 2), e via discorrendo; stiamo quindi parlando di una serie temporale, i cui valori sono strettamente correlati, e che quindi può essere calcolata solo in maniera strettamente sequenziale, dato che uno dei requisiti nel parallel computing è la non interdipendenza tra gli output dell'algoritmo.

Meet mister Bottleneck

Come fare, allora? Ricordiamo ciò che abbiamo detto in precedenza: occorre conoscere il traduttore utilizzato, e l'algoritmo da ottimizzare; partiamo dal primo punto, identificando i colli di bottiglia di cui bisogna essere al corrente.

MATLAB è un linguaggio interpretato: ciò significa che le istruzioni vengono tradotte in codice macchina, una ad una, a runtime (anche se nelle ultime versioni è stata introdotta la traduzione Just In Time, o JIT, che introduce alcune migliorie, pre-compilando ad esempio le porzioni di codice più frequentemente utilizzate). Ed è a questo che sono legati due degli accorgimenti più importanti da usare: evitare, quando possibile, l'uso dei cicli, riducendo il numero di istruzioni da tradurre a runtime, ed organizzare il codice in function, che sfruttano ottimizzazioni interne di cui, purtroppo, è difficile trovare documentazione estesa (ma che probabilmente sono legate anch'esse a routine di pre-compilazione). Un altro collo di bottiglia è legato alla gestione del tipo di dato. MATLAB è infatti un linguaggio non fortemente tipizzato: questo, da un lato, rappresenta un vantaggio per l'utente, dato che semplifica, e di molto, la scrittura del codice; d'altro canto, però, rende necessarie delle operazioni aggiuntive (gestite comunque internamente) per il casting delle variabili, che, inevitabilmente, introducono un overhead più o meno sensibile. Ancora, molto spesso si tende a sottovalutare l'efficienza delle built-in function: esistono infatti delle funzioni, già ottimizzate, di cui lo sviluppatore ignora (deliberatamente o accidentalmente) l'esistenza, 'reinventando la ruota' e, nella maggior parte dei casi, scrivendo codice che fa (peggio) la stessa cosa. Fatte queste debite premesse, è il momento di passare all'analisi del nostro codice. Vediamo insieme i passaggi da seguire.

Profiling!

Generalmente, per migliorare le prestazioni di un manufatto, sia esso un'auto da corsa, un oggetto elettronico, un aereo, o uno script, occorre fare dei test, comprendere dove sono i punti deboli, ed intervenire su di essi. La nostra 'galleria del vento' è rappresentata dai tool di profilazione del codice, che ogni ambiente di sviluppo degno di questo nome mette a disposizione. MATLAB, in particolare, ci offre il Profiler, strumento che analizza i nostri script e function a runtime, valutando i tempi di esecuzione delle singole istruzioni, ed organizzando i risultati in appositi report, che ci offrono un feedback di tipo grafico e semplice da interpretare. Per lanciare il Profiler, rechiamoci nell'Editor, e [...]

ATTENZIONE: quello che hai appena letto è solo un estratto, l'Articolo Tecnico completo è composto da ben 2231 parole ed è riservato agli ABBONATI. Con l'Abbonamento avrai anche accesso a tutti gli altri Articoli Tecnici e potrai fare il download in formato PDF eBook e Mobi per un anno. ABBONATI ORA, è semplice e sicuro.

Scrivi un commento