Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Relatore: Candidato:
Prof. Luigi Brugnano Stefano Brilli
Premessa 7
3 Il Lavoro di Implementazione 35
3.1 Studio iniziale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.1.1 L’ambiente Matlab . . . . . . . . . . . . . . . . . . . . . . 35
3.1.2 L’implementazione Matlab e le strutture dati . . . . . . . . 39
3
3.1.3 Legge di Amdhal . . . . . . . . . . . . . . . . . . . . . . . 41
3.1.4 Prima analisi delle prestazioni . . . . . . . . . . . . . . . . 42
3.2 Prima implementazione . . . . . . . . . . . . . . . . . . . . . . . . 43
3.2.1 Implementazione Host: cmatvec . . . . . . . . . . . . . . . 44
3.2.2 Implementazione CUDA: nvmatvec . . . . . . . . . . . . . 46
3.3 Seconda Implementazione . . . . . . . . . . . . . . . . . . . . . . 48
3.3.1 Implementazione Host . . . . . . . . . . . . . . . . . . . . 50
3.3.2 Implementazione Device . . . . . . . . . . . . . . . . . . . 51
3.4 Implementazione Finale . . . . . . . . . . . . . . . . . . . . . . . 51
3.4.1 Implementazione Host: ccgmamma . . . . . . . . . . . . . . 52
3.4.2 Implementazione Device: nvcgmamma . . . . . . . . . . . . 52
5 Conclusioni 63
Bibliografia 65
4
Lista degli Acronimi
5
6
Dal più antico degli argomenti
trarremo la più nuova delle scienze
Hermann Ebbinghaus
Premessa
• Nella prima parte di questa tesi si illustra brevemente cosa sono le GPU, la
1
NVIDIA Corporation è una azienda leader nella produzione di processori grafici, schede madri
e in generale di prodotti multimediali per personal computer e console.
2
I dati si riferiscono alla GPU di NVIDIA GT200 e alla CPU Intel
R
XeonTM Harpertown
7
loro evoluzione negli anni e quali sono stati i motivi e che hanno condotto
ad un loro utilizzo nel calcolo parallelo.
8
Introduzione alla Grafica
Computazionale Tridimensionale
9
Figura 0.1: Pipeline di rendering di una scena tridimensionale.
10
dei suoi poligoni. Durante le fasi successive, i valori dell’illuminazione ai
vertici di un oggetto vengono usati per interpolare i valori di illuminazione
della sua superficie.
6
L’antialiasing è una tecnica per ridurre l’effetto aliasing (scalettamento) che si ha quando un
segnale a bassa risoluzione viene mostrato ad alta risoluzione. In questo caso l’antialiasing
ammorbidisce le linee, smussando i bordi e omogeneizzando l’immagine finale.
11
Figura 0.2: A confronto l’architettura di una CPU multicore, con quella di un
GPU moderna: a ugual colore corrisponde ugual funzionalità. Nella
GPU la maggior parte della superficie del core è dedicata ad unità
Arithmetic and Logical Unit (ALU)
12
1
Architettura di una GPU
Questo capitolo si occupa di descrivere cosa sono le GPU, come queste si sono
evolute durante gli anni, e quali sono i compiti a cui assolvono all’interno delle
moderne schede grafiche.
I primi chip grafici risalgono all’inizio degli anni 80 e le loro funzioni erano
molto limitate; mancavano totalmente di funzioni per l’elaborazione di scene tri-
dimensionali e avevano un insieme limitato di funzioni per il disegno di scene
bidimensionali.
All’inizio degli anni 90 invece i chip grafici furono sostituiti con vere e proprie
CPU, opportunamente modificate e programmate per fornire le funzioni di di-
segno richieste soprattutto da applicazioni Computer Aided Design (CAD). In
questi anni, anche molte stampanti della Apple hanno utilizzato processori di que-
sto tipo che, a partire da un documento PostScript, producevano un’immagine
bitmap.
Successivamente è stato possibile sviluppare dei chip grafici integrati, i quali for-
nivano tutte le funzioni di accelerazione richieste dalle applicazioni di disegno bidi-
mensionale. Erano meno flessibili delle CPU programmabili ma, il costo inferiore,
13
TM
la maggiore semplicità di fabbricazione e l’avvento di Microsoft
c
Windows ne
facilitarono la diffusione. Quest’ultimo software, infatti, stimolò significativamen-
te l’interesse per la grafica bitmap e, pochi anni dopo, la S3 Graphics
c
introdusse
sul mercato il primo chip di accelerazione 2D; in breve tempo le più costose CPU
grafiche uscirono dal commercio.
A metà degli anni 90 l’uso della grafica tridimensionale iniziò a diffondersi sia
nel mercato delle console di gioco che in quello dei personal computer, spingendo
i produttori ad integrare nei chip grafici diverse funzioni di accelerazione 3D. Una
forte influenza all’implementazione di queste ultime è stata data dalla libreria gra-
fica Open Graphics Library (OpenGL), già apparsa nei primi anni 90. Durante
questo periodo, le implementazioni software dei metodi di OpenGL si semplifica-
rono drasticamente grazie ad un supporto hardware da parte dei chip in continua
crescita. Un’altra libreria che, più tardi negli anni, ha influenzato l’evoluzione
TM
delle GPU è stata la DirectX di Microsoft. L’approccio meno popolare del
supporto scheda-per-scheda, inizialmente, ne rallentò lo sviluppo. Questo riprese
vigore quando Microsoft iniziò a lavorare affianco ai progettisti hardware, pro-
grammando le nuove release 1 dei DirectX contemporaneamente con nuove schede
video compatibili. La versione 7.0 delle librerie DirectX introdusse il primo sup-
porto per l’accelerazione hardware alle trasformazioni ed alle illuminazioni delle
scene. Fu uno dei primi passi a segnare la trasformazione della pipeline grafica
all’interno della GPU, che avrebbe visto a breve l’introduzione dello shading.
14
1.2 Il GPU Computing
L’idea di sfruttare la capacità di calcolo parallelo delle GPU, per compiti differenti
dal rendering, ha portato alla nascita del GPU computing. Le unità di shading, in
particolar modo quelle dedicate ai vertici, sono infatti costruite per l’esecuzione
in parallelo di semplici applicazioni (gli shader ), su una grande quantità di dati.
Con l’ultima generazione di GPU le cose sono cambiate. I diversi tipi di sha-
der, che in passato venivano eseguiti su unità diverse e separate fra di loro, adesso
vengono eseguiti su una sola unità di calcolo. Questa unità è capace di elaborare
l’Unified Shader Model, ovvero un modello di shading che utilizza lo stesso insie-
me di istruzioni per la definizione di fragment, vertex o geometry shader. Questa
soluzione presenta molti vantaggi per le applicazioni di rendering, a cominciare
dalla distribuzione del carico di lavoro. Infatti, se nella generazione precedente di
GPU si poteva presentare il caso in cui un tipo di unità di shading era sovracca-
ricata rispetto alle altre, adesso è possibile allocare dinamicamente più risorse al
compito più impegnativo.
15
−→
Dati
SISD SIMD
←−
Istruzioni
MISD MIMD
16
Figura 1.1: Pipeline di un processore vettoriale a confronto con la pipeline di
un processore scalare: nel primo le operazioni di fetch, decodifica,
attivazione della memoria, e writeback sono eseguite una sola volta.
MISD Multiple Instruction on Single Data è una classe di architetture mai svi-
luppata sul piano commerciale ma solo in alcuni progetti di ricerca per
l’elaborazione di segnali.
17
ha scelto un approccio differente. Infatti se nella propria GPU, AMD usa dei
processori vettoriali, NVIDIA adotta degli Stream Multiprocessor (SM). I dettagli
degli SM e dell’architettura di NVIDIA saranno mostrati nel capitolo successivo,
mentre per adesso è sufficiente sapere che questi non sono altro che processori
multicore, ovvero processori contenenti più unità di elaborazione le quali sono in
grado di lavorare in parallelo e comunicare tra di loro.
La scelta di una tale soluzione è stata motivata da NVIDIA attraverso lo studio
di centinaia di shader appartenenti a programmi di grafica e giochi per i computer.
Gli shader, negli ultimi anni, hanno man mano adoperato un numero sempre
maggiore di operazioni scalari, a scapito delle operazioni vettoriali. Soprattutto
con gli shader più complessi, gli SM hanno dimostrato di essere più efficienti
rispetto ai processori vettoriali nell’eseguire questo tipo di operazioni.
Nel capitolo successivo si studia la soluzione per il GPU computing proposta
da NVIDIA. Le ragioni di questa decisione dipendono in parte dall’hardware che
l’autore ha avuto a disposizione per lo svolgimento di questo studio, ed in parte
dal grande lavoro che NVIDIA ha svolto riguardo alla documentazione della pro-
pria piattaforma. Inoltre, quest’ultima, grazie all’uso degli SM, prometteva una
maggiore flessibilità rispetto alla soluzione concorrente offerta da AMD.
18
2
Il GPU computing di NVIDIA
Tesla, schematizzata in Figura 2.1, è il nome dell’architettura che sta alla base
di GPU come la G80 e della più recente GT200. Le sue componenti principali
consistono in una memoria Dynamic Random Access Memory (DRAM) e in uno
Scalable Processor Array (SPA), ovvero la componente che si occupa di esegui-
re tutte le operazioni programmabili sulla GPU. Nello schema il lavoro fluisce
dall’alto verso il basso, attraversando tutte le componenti, alcune delle quali ope-
rano esclusivamente nel contesto del rendering e rimangono quindi inutilizzate
per le operazioni di computing. Di queste componenti si citeranno soltanto le più
importanti, mentre ci si soffermerà maggiormente su quelle che hanno un ruolo
determinante nell’uso della GPU a fini computazionali.
19
Figura 2.1: Architettura della GPU G80 di NVIDIA
20
i trasferimenti dei dati da e verso la memoria della CPU, l’interpretazione i co-
mandi dell’Host ed il controllo della loro consistenza. Successivamente il Compute
work distribution si occupa della distribuzione sullo SPA del flusso di istruzioni
generato dall’esecuzione dei kernel, ovvero le funzioni che vengono eseguite sulla
GPU. Questo compito è analogo a quello dei Pixel e Vertex work distribution
i quali, invece, distribuiscono il lavoro di shading nelle fasi del rendering grafi-
co. Al termine dell’elaborazione, nel caso che fossero state eseguite operazioni
di rendering, i risultati dei calcoli passano ai Raster Operation Processor (ROP)
attraverso una rete di connessioni. I ROP hanno il compito di eseguire le fun-
zioni non programmabili di colorazione dei pixel, ed operano direttamente sulla
memoria DRAM alla quale sono connessi.
Stream Multiprocessor
21
Figura 2.2: Texture/Processor Cluster nella CPU G80
22
Unità delle texture
Nel contesto del GPU computing, l’unità delle texture è una componente che per-
mette di accedere in sola lettura della memoria DRAM. Questa è dotata di un
meccanismo di caching 1 tale da privilegiare gli accessi localizzati ai dati. Al-
l’interno di un TPC, ogni unità serve contemporaneamente due SM, fornendo
un’alternativa all’uso dello SMC per la lettura dei dati.
23
ni di ciascuno di questi vengono serializzate, con un conseguente degrado delle
prestazioni dovuto all’aumento della latenza.
24
quando l’applicazione è composta da molti thread. La GPU infatti viene utilizza-
ta a pieno solo quando i thread sono nell’ordine delle migliaia, grazie al fatto che
questi hanno un costo di creazione e gestione praticamente nullo.
L’architettura lavora assegnando ad ogni flusso di istruzioni un SP core, in mo-
do che ogni thread venga eseguito indipendentemente dagli altri, conservando il
proprio stato dei registri e indirizzo delle istruzioni. Ogni SM gestisce ed esegue
simultaneamente fino a 24 gruppi, chiamati Warps, di 32 thread ciascuno, per i
quali la MT Issue si occupa di selezionare e inoltrare le istruzioni. Durante l’ese-
cuzione di un programma è possibile che, in presenza di un’istruzione condizionale
(if-else, switch-case, ecc. . . ), due o più thread, all’interno dello stesso Warp,
seguano percorsi differenti. Quando tutti i 32 thread di un Warp seguono lo stesso
ramo di esecuzione, questa architettura raggiunge il massimo dell’efficienza per il
fatto che tutti gli SP cores di un SM eseguono la medesima istruzione. Invece,
se i thread all’interno di un Warp divergono, le prestazioni decadono. Infatti,
quando si presenta quest’ultimo caso, la MT Issue esegue in serie le istruzioni per
i diversi flussi, fintanto che questi rimangono separati. Scrivere un software che
tenga conto di questa caratteristica è fondamentale se si vuole ottenere il massimo
delle prestazioni dalla GPU di NVIDIA.
Nonostante la maggiore efficienza, NVIDIA non vede in un’architettura SIMT
la soluzione definitiva per le proprie GPU, poiché essa introduce diversi problemi
rispetto ad architetture più semplici. Facendo un confronto con la classe dei
sistemi SIMD è infatti richiesta una maggiore logica di controllo, che comporta un
aumento della superficie del chip e un maggior consumo di energia. È sufficiente
osservare i dettagli tecnici delle architetture, prima di AMD, e poi di NVIDIA,
per vedere l’enorme differenza nella superficie dei chip che, nel primo caso, si
limita a 192mm2 contro i 484mm2 del secondo.
25
Nell’elenco seguente si mostrano le differenze principali tra l’implementazione
interna di Tesla e le specifiche ufficiali dello standard. La lista completa di queste
differenze si può consultare in [4].
Queste ed altre piccole differenze possono portare ad una discordanza con i ri-
sultati dei calcoli eseguiti da una CPU. Tuttavia gli errori di moltiplicazione
e addizione non superano mai 0,5 ulp (Unit in the Last Place o precisione di
macchina), ed errori maggiori si possono verificare solo nel calcolo di funzioni
trascendentali, i quali però restano intorno a 2 - 3 ulp.
2.2 CUDA
• Memoria condivisa,
• Sincronizzazione a barriera.
26
Questi vengono controllati dal programmatore mediante poche estensioni al lin-
guaggio C, facilitando cosı̀ l’apprendimento per chi ha già familiarità con questo
ambiente.
Ogni thread possiede una variabile threadIdx, accessibile all’interno del kernel
ed inizializzata univocamente rispetto alla stessa variabile degli M thread dello
stesso blocco. threadIdx è una variabile avente 3 componenti intere, cosı̀ che
i thread possano essere organizzati in spazi monodimensionali, bidimensionali o
tridimensionali. Questo permette al programmatore di scegliere la struttura di
esecuzione del kernel più conveniente, in base al problema da risolvere.
Il codice di esempio 2.1 mostra come si effettua la somma tra due due vettori di
lunghezza M, A e B, ponendo il risultato in un terzo vettore C. Ogni thread somma
una componente di A con la rispettiva componente di B e ne scrive il risultato in
C. Essendo i thread organizzati in uno spazio monodimensionale, la componente
dei vettori sulla quale opera ognuno di questi è data dal primo dei tre valori di
threadIdx. Durante l’esecuzione, per ogni thread, questo valore sarà distinto e
compreso tra 0 e M-1.
27
Figura 2.5: Gerarchia dei thread in CUDA.
}
host int main(){
vecAdd<<<1, M>>>(A, B, C);
}
28
kernel e contenente il numero di blocco a cui appartiene il thread stesso. Questa
variabile è composta da una o due componenti, a seconda che i blocchi siano
organizzati logicamente in uno spazio monodimensionale o bidimensionale.
Configurazione di esecuzione
Il numero dei blocchi che verranno eseguiti sulla GPU, ed il numero dei thread per
ogni blocco, determinano la configurazione di esecuzione di un kernel. All’interno
di un kernel, è possibile conoscere tale configurazione accedendo alle variabili
blockDim e gridDim: la prima contenente il numero di thread per blocco e la
seconda contenente il numero di blocchi per il kernel in esecuzione.
L’architettura Tesla della GPU G80 limita il numero di thread per blocco a
512, e prevede al massimo la gestione 65536 blocchi contemporaneamente. Lo
scheduling dei blocchi di un kernel è gestito in hardware, sulla base di un algoritmo
che di volta in volta assegna ogni blocco ad un solo SM, e ad ogni SM assegna
contemporaneamente fino ad 8 blocchi, la cui dimensione complessiva non supera
però i 768 thread (24 warps). Questi blocchi vengono eseguiti in concorrenza fino
a che lo scheduler decide di sospenderne l’esecuzione e assegnare allo SM dei nuovi
29
blocchi. Per calcolare l’efficienza di una configurazione si introduce il concetto di
occupazione, il quale è facilmente esprimibile come il rapporto tra il numero di
warps attivi e il numero massimo di warps gestibili da un SM,
active warps
occupacy = .
max active warps
30
Figura 2.6: Gerarchia della memoria in CUDA.
Memoria globale Questa è la memoria che i thread utilizzano, sia per leggere i
dati da elaborare, sia per scrivere i risultati delle loro operazioni. Essa è
implementata nella DRAM, quindi i suoi tempi di accesso, come già visto
durante lo studio dell’architettura Tesla, sono centinaia di volte superiori a
quelli dei registri: è importante ridurne l’utilizzo il più possibile.
31
i-esima di uno spazio allineato ad un indirizzo multiplo delle dimensioni di
un half-warp, ovvero 16.
Operazioni di lettura e scrittura coalesced hanno tempi inferiori di circa 10
volte rispetto ad operazioni di lettura e scrittura non strutturate.
32
un insieme di dati sufficientemente piccolo da essere contenuto interamente
nella cache.
Gli spazi di memoria globale, costante e delle texture sono consistenti tra la chia-
mata di un kernel e un altro. Al contrario, i contenuti della memoria locale e
della memoria condivisa sono eliminati al termine di ogni esecuzione.
2.2.3 Sincronizzazione
Thread all’interno dello stesso blocco possono cooperare tra di loro, condividendo
dati attraverso la memoria condivisa, e sfruttando la primitiva di sincronizzazione
a barriera syncthreads(). Quest’ultima, dopo essere stata chiamata all’interno
di un thread, ne blocca l’esecuzione, fino a che tutti gli altri thread appartenenti
allo stesso blocco non l’hanno invocata a loro volta. L’uso di syncthreads()
assume un ruolo determinante quando, ad un dato momento del programma,
si vuol essere sicuri che tutti i processi di un blocco abbiano terminato il loro
compito; ad esempio la scrittura dei propri dati in memoria. Questa funzione è
l’unico meccanismo di sincronizzazione offerto da CUDA.
33
2.2.5 Uso della piattaforma
Figura 2.7: Flusso delle operazioni per un’applicazione costruita con CUDA
34
3
Il Lavoro di Implementazione
Il software che si intende portare su questa piattaforma, come già accennato nel-
l’introduzione, è una implementazione ad-hoc del Metodo dei Gradienti Coniugati
per matrici pentadiagonali simmetriche e definite positive (sdp). Questo è un me-
todo iterativo per la risoluzione di sistemi lineari che, ad ogni passo, esegue sia
delle operazioni scalari, sia una serie di operazioni tra vettori. Fra queste vi sono
la somma membro a membro, la moltiplicazione per uno scalare, il calcolo della
norma e un prodotto matvec. Molte di queste sono operazioni completamente
parallelizzabili e quindi si adattano perfettamente alla natura del modello CUDA
secondo il quale ogni kernel è eseguito contemporaneamente da migliaia di thread.
35
le sue istruzioni non vengono eseguite nativamente1 , ma vengono eseguite dopo
essere state tradotte appositamente per l’architettura sottostante. Questa solu-
zione permette ai software scritti in questo linguaggio di essere completamente
indipendenti dalla piattaforma di esecuzione, rinunciando però ad una parte delle
prestazioni.
Matlab fornisce uno script di compilazione e dei file di inclusione per l’inte-
grazione dei sorgenti. Questi ultimi, per essere compilati all’interno di Matlab,
devono implementare la funzione
void mexFunction(int nlhs, mxArray ∗plhs[ ],int nrhs, const mxArray ∗prhs[ ]).
nrhs Variabile di tipo intero contenente il numero dei parametri di input passati
alla chiamata del metodo dall’ambiente Matlab.
36
mxArray è una struttura che rappresenta una matrice di valori, tutti appartenenti
allo stesso tipo di dato. Questa struttura contiene le informazioni sul numero di
righe, numero di colonne e l’indirizzo di memoria nel quale sono memorizzati i
suoi valori. A tale indirizzo di memoria, i dati sono memorizzati in modo contiguo
per colonne; ci si ritrova quindi a lavorare con un vettore di lunghezza m × n,
referenziato all’interno di una struttura analoga alla seguente.
a1,1 a1,2 . . . a1,n
a1,1 a2,1 . . . am−1,n am,n
a2,1 a2,2 . . . a2,n
m =: .
. .. ... ..
. . .
n
am,1 am,2 . . . am,n
Si termina quindi questa sottosezione con tre esempi. Questi mostrano come
l’operazione di elevazione al quadrato degli elementi di un vettore può essere
integrata in Matlab; prima con linguaggio Matlab poi con linguaggio C ed infine
in ambiente CUDA.
Matlab Il linguaggio Matlab, grazie alla sua flessibilità consente di eseguire l’o-
perazione con una sola riga di codice senza doversi preoccupare della com-
pilazione.
37
C L’integrazione con il linguaggio C richiede, sia una gestione più elaborata dei
dati di input, sia la gestione manuale della memoria per l’allocazione dei
valori di output. I controlli di coerenza, tra i parametri in ingresso e quelli
che la funzione assume di ricevere, devono essere eseguiti manualmente.
Inoltre, prima di richiamare il metodo cquadrato dall’ambiente Matlab, è
necessario compilare il sorgente con il comando mex cquadrato.c.
for ( i=0;i<len;i++)
output[i]=input[i]∗input[i]; /∗elevo al quadrato∗/
}
38
float ∗ output=(float∗)mxGetPr(plhs[0]);
float ∗ input=(float∗)mxGetPr(prhs[0]);
float ∗ device;
cudaMalloc((void∗∗)&device,len∗sizeof(float));
cudaMemcpy(device,input,len∗sizeof(float),cudaMemcpyHostToDevice);
cudaMemcpy(output,device,len∗sizeof(float),cudaMemcpyDeviceToHost);
cudaFree(device);
}
39
function v = matvec( Hdiag, Hipm12j, Hijpm12, p, flag )
%
% Effettua il prodotto Matrice−vettore. Se flag e’ specificato , si assume
% che la diagonale sia unitaria (scalamento diagonale).
%
if nargin==4 % diagonale non unitaria
v = Hdiag.∗p;
else
v = p; % diagonale unitaria
end
v (:,1: end−1) = v(:,1:end−1) −Hijpm12.∗p(:,2:end);
v (:,2: end) = v(:,2:end) −Hijpm12.∗p(:,1:end−1);
v(1:end−1,:) = v(1:end−1,:) −Hipm12j.∗p(2:end,:);
v(2:end ,:) = v(2:end,:) −Hipm12j.∗p(1:end−1,:);
return
• Hdiag = m × n
• Hipm12j = (m − 1) × n
• Hijpm12 = m × (n − 1)
• b = m×n
• etan = m × n
40
A questi parametri, se ne aggiunge uno: il parametro mask. Questo parametro
rappresenta un vettore composto da elementi unitari o nulli. Il suo scopo è quello
di identificare e mascherare alcune condizioni del sistema (3.1). Infine i parametri
maxit, tol e zero corrispondono rispettivamente al numero massimo di iterazioni,
la tolleranza sulla norma del gradiente, e il valore di soglia per il troncamento dei
numeri durante lo scalamento.
41
Figura 3.1: Effetto della legge di Amdhal.
1
S= .
fc
(1 − fc ) +
Sc
Alla luce della legge di Amdhal, per conoscere quali fossero le operazioni più in-
cidenti sui tempi d’esecuzione del software, si sono usati i profiler forniti con gli
ambienti di sviluppo Matlab e CUDA. Un profiler, in breve, è un software che, da-
ta l’esecuzione di un programma, produce in output una serie di informazioni che
la riguardano, ad esempio: il numero di volte che una funzione è stata invocata, o
quanto tempo è stato speso all’interno di questa. Con queste informazioni si è in
grado di individuare facilmente le frazioni di utilizzo delle componenti. In Tabella
3.1 si riporta l’analisi eseguita dal profiler dell’ambiente Matlab sull’esecuzione
del metodo cgmamma per un sistema di dimensioni 512 × 512.
42
Linea Codice Chiamate Tempo (s) Tempo %
16 Ap=matvec([],D1,D2,p,1).*mask; 443 11.188 74.1
17 d=sum(sum(p.*Ap)); 443 1.101 7.3
19 x=x+lam*p; 443 0.852 5.6
22 eta=sum(sum(r.*r)); 443 0.829 5.5
20 r=r-lam*Ap; 443 0.521 3.4
Altre ... 0.616 4.1
Totale 15.107 100
1
= 3, 86.
1 − 0.741
43
Quando si tratterà dell’implementazione per il Device dell’intero Metodo dei
Gradienti Coniugati, si vedrà come l’ideale consista nell’allocare i dati in memoria
soltanto una volta. Questa soluzione è applicabile quando almeno il ciclo princi-
pale del Metodo dei Gradienti Coniugati è interamente eseguito in una funzione
esterna, in modo che tutti i passi iterativi vengano eseguiti al di fuori dell’am-
biente Matlab. Cosı̀, oltre ad eliminare i continui trasferimenti di memoria fra
Host e Device, vengono eliminati anche gli overhead di gestione delle chiamate.
Le misure degli speed up, per le implementazioni in questa sezione, sono state
condotte modificando i metodi in modo che questi ripetessero ad ogni invocazione
10.000 volte il prodotto matrice–vettore. In questo modo non si sono introdotti
overheads dovuti alla gestione delle chiamate. Sulla base dei tempi misurati si
sono calcolate le accelerazioni delle componenti. È importante specificare che i
tempi di esecuzione sono riferiti all’hardware della macchina su cui si è sviluppato
il software: una CPU Intel Core 2 Duo T7300 a 2.00GHz e una GPU NVIDIA
GeForce 8400GS la prima con una memoria operante a 667MHz e la seconda con
con una memoria ed un core operanti a 400 MHz.
L’implementazione Host (listato ??) è costituita da due funzioni, di cui una co-
stituisce il punto di entrata della function, mentre l’altra è quella che effettua il
prodotto matrice-vettore.
Nella prima si effettua un semplice controllo sul numero e sulla dimensione dei
parametri ricevuti in ingresso. Si alloca poi lo spazio per la memorizzazione dei
risultati e si effettua la chiamata alla funzione cmatvec.
Nella seconda si esegue la moltiplicazione della diagonale principale hdiag
quando il parametro intero flag ha valore diverso da 1. Successivamente si ese-
gue la moltiplicazione delle diagonali hijpm12 e hipm12j. La moltiplicazione
delle diagonali con il vettore p viene effettuata in tre cicli separati, che, ogni
volta, scorrono l’array v, eseguendovi le operazioni elemento per elemento.
44
Figura 3.2: Moltiplicazione con Figura 3.3: Moltiplicazione con
la diagonale princi- le sottodiagonali più
pale esterne
45
più esterna. La prima moltiplicazione avviene membro a membro con primi
m × (n − 1) elementi di p, mentre e la seconda si effettua sempre membro
a membro, ma con gli ultimi m × (n − 1) elementi di p (Figura 3.3).
46
la funzione di lettura dei risultati. Dato che il punto di entrata è sostanzialmente
identico a quello presentato nell’implementazione Host, ci si sofferma sulle altre
tre funzioni.
i0 = k × N + j, i1 = i0 + M × N, . . . , il = il−1 + M × N, ∀ l t.c. il ≤ m × n
Alla fine del calcolo, si invoca l’ultima funzione che si occupa di copiare il
contenuto del vettore v, residente sul Device, in memoria dell’Host. Inoltre,
sempre quest’ultima funzione, dealloca la memoria utilizzata e rilascia i riferimenti
all’unità delle texture.
47
confronti dell’implementazione Matlab della function matvec. Inoltre, il metodo
nvmatvec, offre prestazioni superiori anche all’implementazione cmatvec, mo-
strando cosı̀ che una GPU di fascia bassa può competere con una CPU di fascia
superiore.
Device Host
Test m×n matvec(s) cmatvec(s) nvmatvec(s)
speed-up speed-up
1 200 × 200 17,98 6,40 5,3 3,39 2,81
2 300 × 300 49,27 17,54 13,08 3,76 3,39
3 400 × 400 122,58 27,43 21,10 5,80 4,46
4 500 × 500 202,41 44,00 35,51 5,70 4,60
5 600 × 600 310,00 66,69 49,48 6,26 4,64
1 100 × 400 18,67 6,04 5,80 3,21 2,91
2 100 × 900 59,59 15,02 11,57 5,15 3,96
3 100 × 1600 125,63 28,90 21,76 5,77 4,34
4 100 × 2500 210,45 46,78 32,72 6,41 4,49
5 100 × 3600 320,78 71,55 47,82 6,69 4,48
1 400 × 100 17,82 6,49 5,6 3,18 2,74
2 900 × 100 53,83 14,63 13,06 4,12 3,67
3 1600 × 100 119,94 27,6 22,01 5,40 4,34
4 2500 × 100 202,51 44,15 35,81 5,65 4,58
5 3600 × 100 309,95 66,97 49,48 6,26 4,62
48
L’idea, in questa seconda implementazione, consiste infatti nel ridurre la com-
plessità delle operazioni di moltiplicazione. Memorizzando nella struttura dati
hipm12j gli elementi nulli, omessi in precedenza, della prima sottodiagonale del
sistema (3.1), si ottiene un allineamento più favorevole. Nel nuovo schema, in
figura 3.5, si osserva infatti che gli scostamenti dei vettori sono costanti rispetto
all’elemento di v che si sta considerando.
49
L’uso di questa nuova struttura dati non si è dimostrata conveniente se applica-
ta in ambiente Matlab. Infatti, modificando la function matvec per operare con
la struttura dati appena descritta, si ha un netto calo di prestazioni, dipendente
probabilmente dal meccanismo di accesso alla memoria.
Questa nuova implementazione per l’Host cambia rispetto alla precedente sola-
mente nella parte riguardante il prodotto della prima sottodiagonale. Nel Listato
?? si possono osservare in dettaglio le differenze, che sono limitate all’ultimo ciclo
della funzione cmatvec.
Device Host
Test m×n matvec(s) nvmatvec(s) cmatvec(s)
speed-up speed-up
1 200 × 200 17,98 3,92 3,47 4,58 5,18
2 300 × 300 49,27 9,32 13,29 5,28 3,71
3 400 × 400 122,58 15,64 23,83 7,83 5,14
4 500 × 500 202,41 24,93 37,72 8,12 5,37
5 600 × 600 310,00 33,96 57,38 9,13 5,40
1 100 × 400 18,67 4,41 3,49 4,23 5,34
2 100 × 900 59,59 8,70 7,95 6,84 7,49
3 100 × 1600 125,63 14,76 21,44 8,51 5,85
4 100 × 2500 210,45 23,31 37,95 9,02 5,54
5 100 × 3600 320,78 32,12 57,62 9,98 5,56
1 400 × 100 17,82 4,80 5,32 3,71 3,34
2 900 × 100 53,83 8,85 7,99 6,08 6,73
3 1600 × 100 119,94 16.01 23,80 7,49 5,03
4 2500 × 100 202,51 25,39 38,07 7,97 5,31
5 3600 × 100 309,95 34,62 58,02 8,95 5,34
50
3.3.2 Implementazione Device
Con la nuova struttura dati, la moltiplicazione del vettore hipm12j con il vet-
tore p è semplificata da un allineamento dei dati favorevole, che permette l’uso
di scostamenti costanti. Infatti, con questo nuovo allineamento, l’aggiornamento
della componente i-esima del vettore v coinvolge gli elementi i − 1 ed i + 1 di
p, e gli elementi i e i + 1 di hipm12j. La nuova implementazione usa due spazi
allocati in memoria condivisa, nei quali, il thread j-esimo, alla locazione j-esima,
salverà i valori di p[i] e hipm12j[i]. Ogni thread in questo modo potrà ricavare
i valori i − 1 e i + 1 dei vettori p e hipm12j accedendo alla memoria condivisa.
Si presentano solamente due casi speciali, ovvero il primo e l’ultimo thread del
blocco, i quali saranno obbligati ad accedere alla memoria globale.
Dopo aver osservato le tecniche di sviluppo utilizzate per questi modelli di pro-
grammazione, si presenta adesso l’implementazione complessiva del Metodo dei
Gradienti Coniugati. Anche in questo caso, si sono realizzate due versioni del soft-
ware, una per l’Host e una per il Device, le cui performance verranno analizzate
in dettaglio nel capitolo successivo.
51
Per far questo, oltre al metodo matvec, è stato necessario riscrivere, sia per
l’Host sia per il Device, una serie di funzioni. Fra queste, quelle per l’esecuzione di
semplici operazioni di addizione, moltiplicazione e calcolo della norma di vettori,
di cui si osserveranno brevemente le implementazioni; soprattutto nel caso del
Device.
L’implementazione Host, consultabile nel listato ??, non aggiunge niente di nuovo
a quanto visto fino ad ora. Le funzioni di moltiplicazione e addizione dei vettori
sono principalmente composte da un unico ciclo, che esegue le operazioni elemento
per elemento. Quando è stato possibile, è stata utilizzata la tecnica di unrolling
dei cicli, affinché il compilatore potesse utilizzare le istruzioni vettoriali presenti
nella CPU; questo ovviamente per migliorare le prestazioni.
parte 1 Si esegue il prodotto matvec accedendo agli elementi dei vettori p, hijpm12,
hipm12j e mask, calcolando cosı̀ il vettore v. Sfruttando poi gli elementi di p
e v già salvati nei registri, si calcola il loro prodotto scalare, implementando
cosı̀ le istruzioni matvec([],D1,D2,p).*mask e d=sum(sum(p.*Ap)).
52
parte 3 Si eseguono le operazioni x=x+lam*p e p=r+mu*p, le quali sono state
raggruppate nello stesso kernel perché entrambe accedono agli elementi del
vettore p; in questo modo si riducono gli accessi alla memoria globale.
53
Figura 3.6: Esempio di Parallel Reduction per il calcolo della somma degli
elementi di un vettore.
54
4
Test del software
Si inizia confrontando la precisione delle soluzioni trovate dai tre metodi, per
il sistema (3.1). Il formato dei numeri adoperato nei test è quello a singola
precisione, sia per gli ambienti nativi, sia per l’ambiente Matlab. Test che usano il
formato a doppia precisione sono limitati dall’uso di GPU che hanno una compute
capability di almeno 1.3, mentre l’hardware utilizzato ha una compute capability
pari a 1.1.
Il test inizia con la definizione, all’interno di Matlab, delle due diagonali hijpm12
e hipm12j. Queste avvengono rispettivamente con i comandi
• rand(’seed’ ,0) ;
• Hijpm12=single(rand(m,n−1)∗10);
• Hipm12j=single(rand(m−1,n)∗10);
55
m ed n contengono il numero di righe e il numero di colonne della struttura a
matrice in cui saranno salvati i dati. Si definisce poi la diagonale principale
hdiag con il comando
• mask=single(ones(m,n));
• etan=single(zeros(m,n));
Metodo di valutazione
A questo punto, dopo aver definito tutti i parametri richiesti dalla funzione
cgmamma, si sono risolti alcuni sistemi. Per confrontare gli errori sulle soluzioni
prodotte, si valuta il quadrato della norma del residuo, usando il comando
• res=sum(sum((matvec(Hdiag,Hipm12j,Hijpm12,X)−b).ˆ2)),
nel quale la variabile X contiene la soluzione trovata. In Tabella 4.1 sono riportati
i risultati ottenuti.
56
Function m×n iterazioni
mu kAx − bk22
cgmamma 4 5,44e-5
ccgmamma 512 × 512 100 4 5,27e-5
nvcgmamma 4 8,55e-5
cgmamma 18 4,81e-6
ccgmamma 512 × 512 10 18 4,96e-6
nvcgmamma 18 6,83e-6
cgmamma 46 6,30e-6
ccgmamma 512 × 512 1 46 6,29e-6
nvcgmamma 46 7,18e-6
cgmamma 115 1,37e-5
ccgmamma 512 × 512 0.1 92 1,37e-5
nvcgmamma 92 1,45e-5
cgmamma 273 3,13e-5
ccgmamma 512 × 512 0.01 274 3,13e-5
nvcgmamma 273 3,19e-5
cgmamma 4 2,17e-4
ccgmamma 1024 × 1024 100 4 2,11e-4
nvcgmamma 4 3,43e-4
cgmamma 11 1,96e-5
ccgmamma 1024 × 1024 10 11 1,94e-5
nvcgmamma 11 2,58e-5
cgmamma 33 2,60e-5
ccgmamma 1024 × 1024 1 33 2,59e-5
nvcgmamma 33 2,958e-5
cgmamma 121 5,71e-5
ccgmamma 1024 × 1024 0.1 122 5,75e-5
nvcgmamma 121 6,05e-5
cgmamma 292 1,33e-4
ccgmamma 1024 × 1024 0.01 301 1,37e-4
nvcgmamma 292 1,37e-4
Tabella 4.1: Risultati dei test sulla precisione del risultato. Più basso è il valore
del residuo, maggiore è l’accuratezza della soluzione.
57
il compilatore, e l’hardware adoperato, sono determinanti per i risultati del-
le misurazioni; perciò, di seguito, si riassumono le caratteristiche dell’hardware
utilizzato.
PC1 Il primo personal computer è il sistema portatile sul quale è stato sviluppato
il codice dei metodi. La CPU del sistema è una Intel Core 2 Duo T7300,
operante alla frequenza di 2 GHz. Questa ha una memoria cache da 4MByte
e una memoria centrale di 2GByte, operante a 667 MHz. La scheda video,
invece, è un’economica NVIDIA GeForce 8400 GS, composta da 2 SM e
avente una memoria di 128 MByte. GPU e memoria della scheda operano
entrambe alla frequenza di 400 MHz.
Riguardo alla scelta delle alle configurazioni di esecuzione, si sono provate di-
verse combinazioni fra numero di blocchi e numero di thread per blocco, e, alla
fine, si è adoperata la più “performante”. Va tenuto in considerazione che la
configurazione ottimale varia in base all’hardware utilizzato; ad esempio, la GPU
di PC2, con i suoi 12 SM, ha performance migliori quando si adopera un numero
di blocchi maggiore, rispetto a quello adoperato per la GPU di PC1. Per ogni
test si specificherà quindi di volta in volta la configurazione utilizzata.
Il primo test mette a confronto su PC1 le due function sviluppate per questa
tesi, con la function originale, misurandone i tempi di esecuzione. Il test avviene
all’interno dell’ambiente Matlab R2007a, su un sistema operativo Linux (kernel
2.6.23.1). Il compilatore utilizzato è lo GNU C Compiler (GCC) 4.1.2, sul quale,
in fase di compilazione, sono state abilitate tutte le ottimizzazioni del codice. La
58
configurazione d’esecuzione per la GPU è di 128 blocchi per 128 thread ciascuno:
un totale di 16384 thread.
Assegnate le variabili m, n e maxit, lo script con il quale sono stati eseguiti i
test è il seguente.
Hdiag=single(rand(m,n)∗10); %Definizione dei parametri
Hijpm12=single(rand(m,n−1)∗10);
Hipm12j=single(rand(m−1,n)∗10);
ns=[Hipm12j;zeros(1,n)]; %Nuova struttura con allineamento corretto
etan=single(rand(m,n)∗10);
b=single(rand(m,n)∗10);
mask=single(ones(m,n));
tol=1e−7;
zero=single(0.0);
e=clock; %Esecuzione dei test
A=cgmamma(mask,etan,Hdiag,Hipm12j,Hijpm12,b,tol,maxit,zero);
etime(clock,e)
e=clock;
B=ccgmamma(mask,etan,Hdiag,ns,Hijpm12,b,tol,maxit,zero);
etime(clock,e)
e=clock;
C=nvcgmamma(mask,etan,Hdiag,ns,Hijpm12,b,tol,maxit,zero);
etime(clock,e)
Questo script, non definendo una matrice sdp, invalida la proprietà di convergen-
za del Metodo dei Gradienti Coniugati. In questo modo le function terminano
soltanto dopo aver eseguito il numero massimo di iterazioni.
Al termine di ogni esecuzione, lo script visualizza i tempi di esecuzione relativi
ai metodi, cgmamma, ccgmamma e nvcgmamma. I risultati, per diversi valori di m, n
e maxit, sono riportati in Tabella 4.2.
59
Function m×n iterazioni PC 1 (s) speed up
cgmamma 29,39 1
ccgmamma 512 × 512 1000 6,22 4,72
nvgmamma 4,60 6,39
cgmamma 149,41 1
ccgmamma 512 × 512 5000 31,13 4,79
nvgmamma 22,84 6,54
cgmamma 294,65 1
ccgmamma 512 × 512 10000 61,84 4,76
nvgmamma 46,41 6,39
cgmamma 67,96 1
ccgmamma 1024 × 1024 500 15,91 4,27
nvgmamma 10,03 6,77
cgmamma 135,80 1
ccgmamma 1024 × 1024 1000 32,19 4,21
nvgmamma 18,02 7,36
cgmamma 674,92 1
ccgmamma 1024 × 1024 5000 158,50 4,25
nvgmamma 91,52 7,37
Al termine di ogni test, sia il metodo ccgmamma, che il metodo nvcgmamma, stam-
60
pano a video il tempo impiegato per la loro esecuzione. I risultati sono riportati
in Tabella 4.3
Metodo m×n Iter. PC1 (s) speed up PC2 (s) speed up
ccgmamma 7,11 5,73
512 × 512 1000
nvcgmamma 4,64 1,53 0,48 11,93
ccgmamma 35,35 28,54
512 × 512 5000
nvcgmamma 23,14 1,53 2,42 11,79
ccgmamma 33,98 27,70
1024 × 1024 1000
nvcgmamma 18,15 1,87 1,48 18,72
ccgmamma 168,92 138,11
1024 × 1024 5000
nvcgmamma 90,60 1,86 7,40 18,66
ccgmamma 64,28
1536 × 1536 1000 –1 –
nvcgmamma 3,33 19,30
ccgmamma 320,07
1536 × 1536 5000 – –
nvcgmamma 16,14 19,83
ccgmamma 115,96
2048 × 2048 1000 – –
nvcgmamma 5,82 19,92
ccgmamma 578,07
nvcgmamma 2048 × 2048 5000 – – 29,07 19,89
nvcgmamma 28,61 20,212
Tabella 4.3: Confronto fra i tempi di esecuzione dei metodi sui sistemi PC1 e PC2
61
speed up speed up
CPU (e) × 103 GPU (e) × 103 rapp. GPU
CPU
prezzo prezzo
1 20, 21
150 × 103 = 6, 67 110 × 103 = 183, 73 27, 55
150 110
Tabella 4.4: Confronto del rapporto prestazioni/prezzo fra CPU e GPU
62
5
Conclusioni
63
proseguimento del lavoro di tesi. Infatti, a favore della tecnologia convenziona-
le, i vantaggi di CUDA potrebbero ridimensionarsi di fronte ad implementazioni,
in linguaggio C o linguaggio Matlab, capaci di sfruttare l’architettura multicore
delle moderne CPU. Mentre, a favore della tecnologia CUDA, realizzando un’im-
plementazione capace di sfruttare contemporaneamente le capacità di calcolo di
più GPU, si potrebbe comunque incrementare la convenienza economica. Ancora
più interessante, potrebbe essere lo sviluppo di un’implementazione del Metodo
dei Gradienti Coniugati che sfrutti congiuntamente sia la tecnologia classica, sia
la tecnologia CUDA, per ottenere speed up ancora più significativi.
64
Bibliografia
[2] NVIDIA Corporation. Accelerating MATLAB with CUDA using MEX files.
Relazione tecnica, 2007.
[3] NVIDIA Corporation. CUBLAS Sources, 2008. codice sorgente della libreria
CUBLAS.
[7] Richard Edgar. Advanced CUDA, June 2008. GPU workshop Slides.
[9] Wilson W. L. Fung, Ivan Sham, George Yuan, e Tor M. Aamodt. Dynamic
warp formation and scheduling for efficient gpu control flow. In MICRO ’07:
Proceedings of the 40th Annual IEEE/ACM International Symposium on Mi-
croarchitecture, pp. 407–420, Washington, DC, USA, 2007. IEEE Computer
Society.
65
[10] Tom R. Halfhill. Parallel processing with CUDA. Relazione tecnica,
Microprocessor, 2008.
[12] Won-Ki Jeong e Ross Whitaker. High performance computing on the GPU:
NVIDIA G80 and CUDA, 2007. SCI Institute, University of Utah Slides.
[14] Erik Lindholm, John Nickolls, Stuart Oberman, e John Montrym. Nvidia
tesla: A unified graphics and computing architecture. IEEE Micro, 28(2):39–
55, 2008.
[15] David Luebke. GPU Architecture and Implications, 2007. NVIDIA Research
Slides.
[17] Mathworks. Matlab 7 C and Fortran API Reference. The Mathworks, Inc.,
2007.
[21] John D. Owens, Mike Houston, David Luebke, Simon Green, John E. Stone, e
James C. Phillips. GPU computing. Proceedings of the IEEE, 96(5):879–899,
maggio 2008.
[22] Mark Silberstein, Assaf Schuster, Dan Geiger, Anjul Patney, e John D.
Owens. Efficient computation of sum-products on GPUs through software-
managed cache. In Proceedings of the 22nd ACM International Conference
on Supercomputing, pp. 309–318, giugno 2008.
66
[23] R. Stallman e The GCC Developer Community. Using the GNU Compiler
Collection, 2003. for gcc 4.1.2.
[24] Damien Triolet. Product review: The Nvidia GeForce GTX 280 & 260.
Relazione tecnica, BeHardware, July 2008.
67
68
Elenco delle tabelle
4.1 Risultati dei test sulla precisione del risultato. Più basso è il valore del
residuo, maggiore è l’accuratezza della soluzione. . . . . . . . . . . . . . 57
4.3 Confronto fra i tempi di esecuzione dei metodi sui sistemi PC1 e PC2 . 61
69
70
Elenco delle figure
3.6 Esempio di Parallel Reduction per il calcolo della somma degli elementi
di un vettore. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
71
72
Elenco dei sorgenti
2.1 vecAdd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.2 vecAdd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.1 quadrato.m . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.2 cquadrato.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.3 cuquadrato.cu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.4 cgmamma.m . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
73