Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 22 Scrivere codice (in un linguaggio di alto livello) veloce • I linguaggi di alto livello vengono prima compilati in un byte-code indipendente dall’hardware che poi viene interpretato. • Viceversa linguaggi come il C, Fortran traducono il codice in istruzioni macchina fortemente dipendenti dall’hardware. • Questo fatto penalizza quasi sempre le prestazioni dei linguaggi di alto livello sebbene in qualche caso si possano avere risultati addirittura migliori (ad esempio nella processazione di testi). • In ogni caso è sempre possibile utilizzare delle tecniche ad-hoc che consentano di migliorare l’efficienza dei codici mantenendone però la flessibilità, semplicità etc. Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 23 Scrivere codice MATLAB veloce – 1 MATLAB offre numerose possibilità per velocizzare i suoi codici: • preallocazione • ottimizzazione JIT • vettorizzazione • ... Queste tecniche sono quasi sempre di aiuto ma la loro efficacia va valutata caso per caso. Alcune controindicazioni: • non conviene ottimizzare una parte se questa pesa molto poco all’interno dell’applicazione • un codice estremamente ottimizzato può risultare molto meno comprensibile dell’originale. Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 24 Scrivere codice MATLAB veloce – 2 Vediamo un semplice esempio che mostra come gran parte delle ottimizzazioni da utilizzare in un contesto di codice “compilato” rimangono vere. Supponiamo di avere una matrice A m × n e un vettore x n × 1. Vogliamo formare il vettore y=Ax. In MATLAB scriveremmo semplicemente: >> y=A*x; Vediamo due algoritmi alternativi. Il primo utilizza il concetto di “prodotto scalare” tra le righe della matrice A e il vettore x: yi = A(i, :) ∗ x (i = 1, ..., m) Il secondo invece utilizza le colonne della matrice A in questo modo: A ∗ x = x(1) ∗ A(:, 1) + x(2) ∗ A(:, 2) + ... + x(n) ∗ A(:, n) Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 25 Scrivere codice MATLAB veloce – 3 Sorprendentemente, benchè i due algoritmi hanno lo stesso numero di operazioni, l’“elapsed–time” è notevolmente diverso. Scriviamo emandiamo in esecuzione lo script cachetest.m: c l e a r ; n =2048; A= r a n d ( n ) ; x= r a n d ( n , 1 ) ; y= z e r o s ( n , 1 ) ; d i s p ( ’ primo test ’ ) ; t i c ; f o r i = 1 : n , y ( i ) =A( i , : ) ∗ x ; , end ; t o c d i s p ( ’ secondo test ’ ) ; t i c ; f o r i = 1 : n , y=y+x ( i ) ∗A ( : , i ) ; end ; t o c d i s p ( ’A*x ’ ) ; t i c ; y=A∗ x ; t o c Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 26 Scrivere codice MATLAB veloce – 4 >> cachetest primo test Elapsed time is 0.203873 seconds. secondo test Elapsed time is 0.023361 seconds. A*x Elapsed time is 0.010208 seconds. >> • Il secondo algoritmo è notevolmente più veloce del primo e confrontabile con la funzione built–in. • La differenza di velocità si spiega con il fatto che MATLAB memorizza la matrice per colonne e quindi il primo algoritmo soffre di problemi di “Memory Management” (cache, etc). Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 27 Il Profiler – 1 Questo tool aiuta a determinare i bottlenecks nel codice. Vediamo un semplice esempio: f u n c t i o n e x a m p l e 1 ( Count ) f o r k = 1 : Count result (k) = sin (k /50) ; i f r e s u l t ( k ) < −0.9 r e s u l t ( k ) = gammaln ( k ) ; end end Possiamo mandare in esecuzione la funzione dando il parametro di input (Count): >> example1(5000); Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 28 Il Profiler – 2 Per analizzare l’efficienza della funzione abilitiamo il Profiler: >> profile clear >> profile on >> tic;example1(20000);toc Elapsed time is 1.204688 seconds. >> profreport(’example1’) Il Profiler genera un file HTML e lancia una finestra di report della frazione di tempo di esecuzione speso da ciascuna funzione. Si può andare maggiormente in dettaglio istanziando la funzione con un semplice mouse–click. Il risultato è che il codice spende la maggior parte del tempo nella chiamata della funzione trigonometrica sin: Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso Scuola Estiva di Calcolo Avanzato 2008 MATLAB Avanzato: 29 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 30 Preallocazione – 1 Una delle caratteristiche principali di MATLAB è la cosiddetta allocazione della memoria dinamica. Un esempio: >> a=2 a = 2 >> a(2,6)=1 a = 2 0 0 0 0 0 0 0 0 0 0 1 Internamente la memoria allocata dalla matrice deve essere riallocata. Se quest’operazione avviene all’interno di un ciclo la velocità di esecuzione può risentirne. Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 31 Preallocazione – 2 Per evitare questo problema è buona norma preallocare la memoria richiesta dalla matrice al suo valore massimo. Vediamolo con un semplice esempio: a ( 1 ) =1; b ( 1 ) =0; f o r k =2:20000 a ( k ) = 0 . 9 9 8 0 3 ∗ a ( k −1) −0.06279∗ b ( k −1) ; b ( k ) = 0 . 0 6 2 7 9 ∗ a ( k −1) + 0 . 9 9 8 0 3 ∗ b ( k −1) ; end Mandandolo in esecuzione: >> tic;example;toc Elapsed time is 2.475201 seconds. Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 32 Preallocazione – 3 Cambiamo lo script utilizzando la funzione zeros di MATLAB ed otteniamo un notevole speed–up nelle prestazioni: a= z e r o s ( 1 , 2 0 0 0 0 ) ; b= z e r o s ( 1 , 2 0 0 0 0 ) ; a ( 1 ) =1; b ( 1 ) =0; f o r k =2:20000 a ( k ) = 0 . 9 9 8 0 3 ∗ a ( k −1) −0.06279∗ b ( k −1) ; b ( k ) = 0 . 0 6 2 7 9 ∗ a ( k −1) + 0 . 9 9 8 0 3 ∗ b ( k −1) ; end >> tic;example;toc Elapsed time is 0.006386 seconds. Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 33 Ottimizzazione JIT MATLAB è un linguaggio di alto livello pertanto uno script MATLAB viene processato in due parti: • Il codice MATLAB viene convertito in linguaggio intermedio detto p–code • L’interprete esegue ogni istruzione del p–code in sequenza. Come risultato un certo overhead è associato all’esecuzione di una istruzione del p–code. A seconda dell’istruzione può accadere che l’ overhead sia confrontabile col suo tempo di esecuzione: le prestazioni saranno pertanto pessime! Un altro problema è che l’interprete deve gestire il caso più generale e complicato possibile. Questo fatto genera ulteriore overhead specialmente nel caso di operazioni su valori scalari. Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 34 Ottimizzazione JIT – 2 L’ottimizzazione JIT (Just In Time è una funzionalità built–in di MATLAB che consente di velocizzare codice MATLAB riducendo l’overhead di alcune istruzioni. Si compone di: • Just In Time Code Generation in cui istruzioni p–code vengono convertite in linguaggio macchina. L’overhead risulta drammaticamente ridotto. • Run–time Type Analysis che si basa sul fatto (quasi sempre vero) che se una linea di codice è stata processata, tutte le variabili manterranno stessi tipi e dimensioni in successive chiamate. Viene pertanto generato codice ad–hoc che può essere utilizzato in seguito. Se tipo o dimensione della variabile cambia, il codice sarà rigenerato. Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 35 Ottimizzazione JIT – 3 Trarranno vantaggio da questo tipo di ottimizzazione codici che fanno grande uso di valori scalari o cicli for. In generale codici che non è possibile vettorizzare in modo immediato. Vediamo un semplice esempio: a = 2.5; b = 3.33; c = 6.23; f o r i = 1:1000000 a = b + c; b = a − c; c = b ∗ a; if (a > b) a = b − c; else b = b + 12.3; end , end Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 36 Ottimizzazione JIT – 4 Mandando in esecuzione lo script l’elapsed–time è risibile. Il codice è stato accelerato: >> tic;simple;toc Elapsed time is 0.169312 seconds. Questo tipo di ottimizzazione presenta però il problema di dipendere fortemente dal fatto che le istruzioni all’interno del ciclo for siano operazioni matematiche o al più built–in. Anche una semplice chiamata ad una funzione utente può annullare l’ottimizzazione perchè l’interprete non conosce in anticipo il tipo o dimensione del dato utilizzato nel nuovo script. Cambiamo lo script simple.m introducendo solamente la funzione intdummy che assegna il valore corretto all’indice del ciclo for. Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 37 Ottimizzazione JIT – 5 a = 2.5; b = 3.33; c = 6.23; f o r i = 1 : intdummy a = b + c; b = a − c; c = b ∗ a; if (a > b) a = b − c; else b = b + 12.3; end , end dove: f u n c t i o n d=intdummy d =1000000; Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 38 Ottimizzazione JIT – 6 Il risultato è un codice circa 20 volte più lento dell’originale: >> tic;simplebad;toc Elapsed time is 2.470977 seconds. Altre limitazioni: • Matrici sparse o array 3-D (o più). • Overloading di operatori matematici di default. • Espressioni condizionali if, elseif, while, switch che non valutino uno scalare. Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 39 Vettorizzazione – 1 • Si dice che un calcolo è vettorizzato quando si eseguono delle operazioni sull’intero array piuttosto che elemento per elemento. • La vettorizzazione sfrutta appieno le potenzialità di MATLAB poiché molte funzioni MATLAB sono già vettorizzate: >> A=rand(100); >> B=sqrt(A); • In generale, si richiede la conoscenza delle funzioni built–in già vettorizzate e una buona comprensione del tipo di calcolo che si vuole svolgere. • Non esistono regole generali, va studiato caso per caso. Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 40 Vettorizzazione – 2 Consideriamo la funzione minDistance: function d = minDistance ( x , y , z ) nPoints = length (x) ; d = zeros ( nPoints , 1 ) ; f o r k =1: n P o i n t s d ( k ) = s q r t ( x ( k ) ^2+ y ( k ) ^2+ z ( k ) ^ 2 ) ; end d=min ( d ) ; La funzione built–in length calcola la lunghezza del vettore. Un esempio di utilizzo è il seguente: >> x=rand(10000000,1); y=rand(10000000,1); z=rand(10000000,1); >> tic;d=minDistance(x,y,z);toc Elapsed time is 2.397184 seconds Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 41 Vettorizzazione – 3 Per vettorizzare questa funzione dobbiamo semplicemente rimpiazzare il for loop con un’operazione vettoriale. Il nuovo codice è: function d = minDistance1 ( x , y , z ) d = s q r t ( x .^2+ y .^2+ z . ^ 2 ) ; d=min ( d ) ; Si noti l’operatore · ˆ che agisce sull’intero vettore nel calcolo della funzione distanza. Il nuovo codice è più veloce: >> x=rand(10000000,1); y=rand(10000000,1); z=rand(10000000,1); >> tic;d=minDistance1(x,y,z);toc Elapsed time is 0.45808 seconds e dobbiamo attenderci vantaggi anche maggiori al crescere della dimensione dei dati. Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 42 Logica Vettorizzata Spesso un calcolo realistico è composto, oltre che da una parte computazionale, da una parte dove sono utilizzate operazioni condizionali (logica). Anche questo tipo di operazioni è vettorizzato in MATLAB mediante l’uso di funzioni specializzate come il find. Ad esempio l’istruzione complessa: >> i=find(isnan(x) |isinf(x));x(i)=[]; rimuove dal dataset x i valori NaN ed Inf. Il codice risulta estremamente ottimizzato nonchè compatto. Sarebbe difficile ottenere entrambi gli obiettivi anche con un linguaggio come il C. Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 43 Attenzione alla Vettorizzazione – 1 Questo semplice esempio assegna ad un vettore y la somma dei primi j elementi di un altro vettore x, ossia: y j = x1 + x2 + ... + x j Mandiamo in esecuzione l’algoritmo non vettorizzato: >> clear; n=2000; x = 1:n; x = x(:); y = zeros(n,1); >> t=cputime; for j=1:n, for k=1:j, y(j)=y(j)+x(k); end,end,cputime-t ans = 2.8700 Chiaramente possiamo vettorizzare almeno parzialmente utilizzando la funzione built–in sum, ossia: >> t=cputime; for j=1:n, y(j)=sum(x(1:j)); end, cputime-t ans = 0.0300 Scuola Estiva di Calcolo Avanzato 2008 Piero Lanucara, Fabio Bonaccorso MATLAB Avanzato: 44 Attenzione alla Vettorizzazione – 2 Chiaramente le cose sono molto migliorate ma, sorprendentemente (?): >> t=cputime; y = tril(ones(n))*x; cputime-t ans = 0.1100 le cose peggiorano vettorizzando completamente il codice utilizzando le funzioni built–in tril e ones. Infatti: >> A=tril(ones(4)) A = 1 0 0 1 1 0 1 1 1 1 1 1 0 0 0 1 e di conseguenza vi è un certo numero di moltiplicazioni per 0 in A*x. Scuola Estiva di Calcolo Avanzato 2008