Scrivere codice (in un linguaggio di alto livello) veloce • I

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