+ All Categories
Home > Documents > Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Date post: 08-Jun-2015
Category:
Upload: remcrash
View: 1,267 times
Download: 2 times
Share this document with a friend
Description:
La mia tesi di laurea... tratta di come è possibile utilizzare la tecnologia CUDA di nVIDIA all'interno dell'algoritmo dei gradienti coniugati, per risolvere sistemi la cui matrice associata è pentadiagonale simmetrica e definita positiva (sdp)My Thesis... it's about the use of nVIDIA CUDA technology in conjugate gradient algoritm, to solve pentadiagonal simmetric and definite positive (sdp) linear systems.
73
Universit ` a degli studi di Firenze FACOLT ` A DI SCIENZE MATEMATICHE, FISICHE E NATURALI Corso di Laurea in Informatica Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA (Compute Unified Device Architecture) Tesi di Laurea in Informatica Relatore: Prof. Luigi Brugnano Candidato: Stefano Brilli Anno Accademico 2007-2008
Transcript
Page 1: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Universita degli studi di Firenze

FACOLTA DI SCIENZE MATEMATICHE, FISICHE E NATURALI

Corso di Laurea in Informatica

Implementazione efficiente del Metodo

dei Gradienti Coniugati in ambiente

CUDA (Compute Unified DeviceArchitecture)

Tesi di Laurea in Informatica

Relatore:Prof. Luigi Brugnano

Candidato:Stefano Brilli

Anno Accademico 2007-2008

Page 2: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

2

Page 3: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Indice

Lista degli Acronimi 5

Premessa 7

Introduzione alla Grafica Computazionale Tridimensionale 9

1 Architettura di una GPU 13

1.1 Evoluzione delle GPU . . . . . . . . . . . . . . . . . . . . . . . . 13

1.2 Il GPU Computing . . . . . . . . . . . . . . . . . . . . . . . . . . 15

1.3 Confronto fra le architetture . . . . . . . . . . . . . . . . . . . . . 16

2 Il GPU computing di NVIDIA 19

2.1 L’architettura Tesla . . . . . . . . . . . . . . . . . . . . . . . . . . 19

2.1.1 Il flusso delle operazioni . . . . . . . . . . . . . . . . . . . 19

2.1.2 Componenti dello Scalable Processor Array (SPA) . . . . . 21

2.1.3 Un’architettura di tipo SIMT . . . . . . . . . . . . . . . . 24

2.1.4 Operazioni in virgola mobile . . . . . . . . . . . . . . . . . 25

2.2 CUDA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

2.2.1 Gerarchia dei thread . . . . . . . . . . . . . . . . . . . . . 27

2.2.2 Gerarchia della memoria . . . . . . . . . . . . . . . . . . . 30

2.2.3 Sincronizzazione . . . . . . . . . . . . . . . . . . . . . . . . 33

2.2.4 Compute capability . . . . . . . . . . . . . . . . . . . . . . 33

2.2.5 Uso della piattaforma . . . . . . . . . . . . . . . . . . . . . 34

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

Page 4: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

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

4 Test del software 55

4.1 Precisione dei risultati . . . . . . . . . . . . . . . . . . . . . . . . 55

4.2 Tempi di esecuzione . . . . . . . . . . . . . . . . . . . . . . . . . . 56

4.2.1 Esecuzione in ambiente Matlab/Linux . . . . . . . . . . . 58

4.2.2 Esecuzione in ambiente Windows . . . . . . . . . . . . . . 59

4.3 Rapporto prestazioni/prezzo . . . . . . . . . . . . . . . . . . . . . 61

5 Conclusioni 63

Bibliografia 65

Elenco delle tabelle 69

Elenco delle figure 71

4

Page 5: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Lista degli Acronimi

ALU Arithmetic and Logical Unit

API Application Programming

Interface

CAD Computer Aided Design

CG Computer Graphics

CPU Central Processing Unit

CUDA Compute Unified Device

Architecture

DRAM Dynamic Random Access

Memory

Flops Floating point Operations

Per Second

GCC GNU C Compiler

GPGPU General Purpose computing

on GPU

GPU Graphics Processing Unit

MAD Multiply-Add

MIMD Multiple Instructions on

Multple Data

MISD Multiple Instructions on

Single Data

MT Issue MultiThreaded instruction

fetch and Issue unit

OpenGL Open Graphics Library

ROP Raster Operation Processor

SDK Software Development Kit

SFU Special Function Unit

SIMD Single Instruction on

Multiple Data

SIMT Single Instruction Multiple

Threads

SISD Single Instruction on Single

Data

SMC Streaming Multiprocessor

Controller

SM Stream Multiprocessor

SPA Scalable Processor Array

SP Streaming Processor

TPC Texture Processor Cluster

5

Page 6: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

6

Page 7: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Dal piu antico degli argomenti

trarremo la piu nuova delle scienze

Hermann Ebbinghaus

Premessa

Introdotta ufficialmente nel 2006, la Compute Unified Device Architecture (d’ora

in poi CUDATM

) e una tecnologia realizzata dalla NVIDIA R©1. Questa ha lo scopo

di estendere le capacita di elaborazione delle schede video, a compiti che vanno

oltre la grafica computazionale.

Rispetto alla prima legge di Moore, secondo la quale le prestazioni dei processori

raddoppiano ogni 18 mesi, le Graphics Processing Unit (GPU) delle schede video

hanno dimostrato un raddoppio di prestazioni ogni 6 mesi. Ovvero la prima legge

di Moore elevata al cubo. Questo e stato possibile grazie all’intenso sviluppo delle

architetture parallele sulle quali si basano queste unita. Le loro capacita di calcolo

hanno ormai raggiunto l’ordine del Teraflop (1012 operazioni al secondo) contro gli

attuali 100 GFlop/s della Central Processing Unit (CPU) per personal computer

piu “performante” sul mercato2. E chiaro quindi l’interesse ad apprendere il

funzionamento e le caratteristiche di questo nuovo tipo di unita di calcolo, al fine

di sfruttarne pienamente le potenzialita offerte.

Questo lavoro esamina diversi aspetti della tecnologia CUDA, considerando-

la come una conseguenza diretta del processo evolutivo delle schede video. Si

affronta inoltre la realizzazione di una componente software che utilizza questa

tecnologia, al fine di apprenderne l’utilizzo e valutarne l’efficacia. Tale componen-

te e una implementazione ad hoc del Metodo dei Gradiendi Coniugati per matrici

pentadiagonali, originariamente scritta in linguaggio Matlab. Lo sviluppo di que-

sto software ha dato modo di osservare sia le limitazioni sia le potenzialita di

questa piattaforma.

• Nella prima parte di questa tesi si illustra brevemente cosa sono le GPU, la

1NVIDIA Corporation e una azienda leader nella produzione di processori grafici, schede madrie in generale di prodotti multimediali per personal computer e console.

2I dati si riferiscono alla GPU di NVIDIA GT200 e alla CPU Intel R© XeonTMHarpertown

7

Page 8: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

loro evoluzione negli anni e quali sono stati i motivi e che hanno condotto

ad un loro utilizzo nel calcolo parallelo.

• Nella seconda parte si esamina l’architettura delle unita di calcolo prodotte

dalla NVIDIA ed il modello di programmazione utilizzato dall’ambiente

CUDA.

• Nella terza parte si mostrano gli strumenti di sviluppo, le tecniche di pro-

grammazione e le implementazioni dell’applicativo realizzato.

• Nella quarta parte si eseguono i test sulle implementazioni realizzate.

• Nella quinta ed ultima parte si traggono le conclusioni del lavoro svolto.

8

Page 9: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Introduzione alla Grafica

Computazionale Tridimensionale

La Grafica Computazionale Tridimensionale, o Computer Graphics (CG) 3D, e

una branca della CG che usa dati geometrici tridimensionali per la produzione

di immagini bitmap3. Questo processo e suddiviso in due fasi: la prima, in cui

vengono definiti gli oggetti, le prospettive e le illuminazioni; la seconda, in cui si

produce l’immagine. Fra queste, la seconda fase, chiamata fase di rendering, e

molto piu onerosa in termini di prestazioni rispetto alla prima.

Nei personal computer, quando si tratta di produrre una singola immagine,

entrambe le fasi possono essere assegnate alla CPU. Invece, quando il compito e

quello di produrre dalle 30 alle 60 immagini al secondo, la capacita di calcolo della

CPU non e piu sufficiente. Tali compiti sono soprattutto richiesti dai giochi per

computers, i quali percio, delegano il rendering alle moderne schede video. Negli

anni, grazie allo sviluppo dell’industria videoludica, le GPU di queste schede si

sono evolute fino a diventare dei potenti calcolatori paralleli (Figura 0.2). Sono

state dotate di una propria memoria e hanno acquisito la capacita di svolgere

velocemente le complesse operazioni del rendering.

L’intero rendering comprende diverse fasi, che, raggruppate insieme, formano

la pipeline grafica (Figura 0.1).

1. Si inizia con il trasferimento della descrizione di una scena, dalla memoria

centrale della CPU, alla memoria della scheda video. La descrizione di

una scena comprende: l’insieme di vertici che definiscono gli oggetti, le

texture4 che vi verranno applicate, i dati sull’illuminazione, ed il punto di

osservazione della scena.3Immagine bidimensionale definita da una matrice (mappa) di bit4Una texture e l’immagine bitmap usata per rappresentare la superficie di un oggetto.

9

Page 10: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Figura 0.1: Pipeline di rendering di una scena tridimensionale.

2. La seconda fase e quella in cui vengono eseguite le trasformazioni dei verti-

ci. Le rotazioni, gli scaling e le traslazioni degli oggetti sono fondamentali

per la definizione di una scena. Ad esempio, un designer puo realizzare

contemporaneamente sia il modello di un’automobile sia il modello di un

tavolo, senza preoccuparsi del rapporto fra le loro dimensioni. In questa

fase il primo oggetto sara dimensionato in modo da essere piu grande del

secondo.

3. Alla precedente, segue un’altra fase di trasformazione, necessaria per l’ag-

giunta della prospettiva. In questa fase vengono anche eliminati dalla scena

tutte le parti degli oggetti che, per via della camera5, non compariranno

nell’immagine finale.

4. La quarta fase e quella dedicata all’illuminazione degli oggetti. Nel rende-

ring e una delle fasi piu impegnative dal punto di vista computazionale. Per

questo motivo, quando si parla di rendering in tempo reale, l’illuminazione

di un oggetto, in base alle sorgenti di luce, viene calcolata soltanto ai vertici

5Camera e il termine usato per indicare il punto di vista della scena

10

Page 11: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

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.

5. Si passa quindi alla fase di rasterization, ovvero la trasformazione dei dati

elaborati finora in un’immagine bitmap. Durante questa fase, le coordinate

tridimensionali vengono trasformate in coordinate bidimensionali e le tex-

ture vengono applicate sugli oggetti. Al termine vengono anche applicati

eventuali effetti grafici come ombre, nebbia e antialiasing6, ecc...

6L’antialiasing e una tecnica per ridurre l’effetto aliasing (scalettamento) che si ha quando unsegnale a bassa risoluzione viene mostrato ad alta risoluzione. In questo caso l’antialiasingammorbidisce le linee, smussando i bordi e omogeneizzando l’immagine finale.

11

Page 12: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Figura 0.2: A confronto l’architettura di una CPU multicore, con quella di unGPU moderna: a ugual colore corrisponde ugual funzionalita. NellaGPU la maggior parte della superficie del core e dedicata ad unitaArithmetic and Logical Unit (ALU)

12

Page 13: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

1Architettura 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.

1.1 Evoluzione delle GPU

La GPU e la componente che esegue le operazioni di rendering all’interno di una

scheda video moderna.

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 e 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

Page 14: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

la maggiore semplicita di fabbricazione e l’avvento di Microsoft c© WindowsTM

ne

facilitarono la diffusione. Quest’ultimo software, infatti, stimolo 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 piu costose CPU

grafiche uscirono dal commercio.

A meta degli anni 90 l’uso della grafica tridimensionale inizio 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 e stata data dalla libreria gra-

fica Open Graphics Library (OpenGL), gia 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, piu tardi negli anni, ha influenzato l’evoluzione

delle GPU e stata la DirectXTM

di Microsoft. L’approccio meno popolare del

supporto scheda-per-scheda, inizialmente, ne rallento lo sviluppo. Questo riprese

vigore quando Microsoft inizio a lavorare affianco ai progettisti hardware, pro-

grammando le nuove release1 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.

Lo shading e la capacita della GPU di eseguire degli shader, ovvero un insieme

di istruzioni che definisce un algoritmo di trasformazione. Lo shader puo essere

di vertici, di frammento (o di pixel) e di geometria. Ad esempio, Il vertex shader

si occupa di trasformare la posizione dei vertici di un oggetto, gestendone cosı la

forma e la dimensione. Il fragment shader, invece, elabora i singoli pixel di un

oggetto applicandovi texture ed effetti di colorazione.

NVIDIA e stata la prima azienda a produrre un chip con capacita di shading

programmabili e successivamente ATI R©2 introdusse il supporto a shader conte-

nenti istruzioni di ciclo e di calcolo in virgola mobile. In breve tempo le GPU so-

no divenute flessibili come CPU completamente programmabili ed estremamente

“performanti” nell’esecuzione di operazioni con molti dati.

1In ambito informatico release indica una particolare versione di un software resa disponibileai suoi utenti.

2ATI e stata per molti anni la principale societa concorrente di NVIDIA. Nel 2006 e stataacquistata dalla AMD R©, azienda nota principalmente per la produzione di CPU.

14

Page 15: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

1.2 Il GPU Computing

L’idea di sfruttare la capacita di calcolo parallelo delle GPU, per compiti differenti

dal rendering, ha portato alla nascita del GPU computing. Le unita 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 quantita di dati.

Diversi progetti, come General Purpose computing on GPU (GPGPU), utiliz-

zano le Application Programming Interface (API) di disegno 3D come strumenti

per la programmazione delle GPU. Attraverso queste API e possibile definire uno

shader su insiemi di vertici e texture, in modo tale che questo corrisponda alla

definizione di un’applicazione parallela su un insieme di dati. In questo modo

la GPU, eseguendo lo shader, e in grado di assolvere a compiti general purpose;

ovvero non strettamente legati ad applicazioni di grafica computazionale.

Con l’ultima generazione di GPU le cose sono cambiate. I diversi tipi di sha-

der, che in passato venivano eseguiti su unita diverse e separate fra di loro, adesso

vengono eseguiti su una sola unita di calcolo. Questa unita e 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 unita di shading era sovracca-

ricata rispetto alle altre, adesso e possibile allocare dinamicamente piu risorse al

compito piu impegnativo.

Oltre ai vantaggi per le applicazioni di rendering, grazie a questo radicale cam-

biamento e stato possibile sviluppare piattaforme di GPU computing come lo

Stream SDK di AMD, Larrabee di Intel (in fase di ultimazione) e CUDA di NVI-

DIA, le quali non sono altro che ambienti di sviluppo per la programmazione della

GPU. Questi hanno la caratteristica di non dover piu ricorrere all’uso delle API

di disegno 3D per elaborare i dati attraverso la scheda video. Si servono, infatti,

di API appositamente sviluppate per la programmazione di applicazioni general

purpose, ovvero di uso generico.

15

Page 16: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

−→

←−

Dati

Istr

uzi

oni SISD SIMD

MISD MIMD

Tabella 1.1: Tassonomia di Flynn

1.3 Confronto fra le architetture

Nonostante le principali aziende del settore grafico abbiano realizzato (o stiano

realizzando) un’implementazione dell’Unified Shader Model, le scelte architettu-

rali delle proprie GPU rimangono profondamente diverse. Al momento sono di-

sponibili solo due architetture implementanti l’Unified Shader Model, quella di

AMD e quella di NVIDIA. Per confrontarne le caratteristiche principali si illustra

la tassonomia di Flynn, la quale classifica i sistemi di calcolo in base al numero dei

flussi di istruzioni e dei dati che sono in grado di gestire. Le categorie principali

sono rappresentate in Tabella 1.1 e si distinguono in:

SISD Single Instruction on Single Data e la categoria contenente l’architettura

tradizionale della macchina di Von Neumann, utilizzata da tutti i calcolatori

convenzionali, in cui un solo processore obbedisce ad un singolo flusso di

istruzioni (programma sequenziale) ed esegue queste istruzioni ogni volta

su un singolo flusso di dati.

SIMD Single Instruction on Multiple Data. Alla categoria SIMD appartengono

le architetture composte da molte unita di elaborazione che eseguono con-

temporaneamente la stessa istruzione su un insieme di dati differenti. Ge-

neralmente il modo di implementare queste architetture consiste nell’avere

un processore principale che invia le istruzioni in parallelo ad un insieme di

unita di elaborazione, le quali provvedono ad eseguirle.

Una di queste implementazioni e il processore vettoriale. I processori vetto-

riali sono unita di calcolo che, dopo aver letto e decodificato un’istruzione,

la eseguono su piu dati prima di passare all’istruzione successiva (Figura

1.1). L’unita di esecuzione lavora su registri vettoriali3, il cui contenuto e

3All’interno di un processore, i registri sono piccole quantita di memoria nelle quali vengono

16

Page 17: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Figura 1.1: Pipeline di un processore vettoriale a confronto con la pipeline diun processore scalare: nel primo le operazioni di fetch, decodifica,attivazione della memoria, e writeback sono eseguite una sola volta.

gestito dall’unita vettoriale di load–store. Questa e l’unita che si occupa di

leggere e scrivere dalla memoria centrale ai registri vettoriali. E in grado

di operare su piu dati contemporaneamente e, in un processore vettoriale,

ve ne possono essere piu di una. A causa del funzionamento di quest’ulti-

ma componente, i processori vettoriali hanno lo svantaggio di essere poco

adatti all’elaborazione di dati non distribuiti in modo costante. Quindi le

loro reali prestazioni variano a seconda della tipologia del programma che

si vuole eseguire.

MISD Multiple Instruction on Single Data e una classe di architetture mai svi-

luppata sul piano commerciale ma solo in alcuni progetti di ricerca per

l’elaborazione di segnali.

MIMD Multiple Instruction on Multiple Data e la categoria che comprende le ar-

chitetture in grado di eseguire piu flussi di istruzioni su piu flussi di dati

contemporaneamente, e rappresenta una evoluzione della categoria SISD.

Infatti la realizzazione di queste architetture avviene attraverso l’intercon-

nessione di un numero elevato di elaboratori convenzionali, facendo sı che

a questa classe appartengano sistemi di calcolo multiprocessore e sistemi di

calcolo distribuiti.

In particolare, le categorie SIMD e MIMD, costituiscono la classe delle architet-

ture parallele. Di queste fanno parte le GPU, le quali, come gia visto, si sono

specializzate nell’esecuzione concorrente dello stesso flusso di istruzioni su molti

dati.

Al livello implementativo, sia l’architettura di AMD che quella di NVIDIA

appartengono alla classe dei sistemi SIMD, mentre al livello funzionale NVIDIA

trasferiti i dati, dalla memoria principale, prima di operarvi. Un registro vettoriale e uninsieme di registri sui quali si opera contemporaneamente con una singola istruzione.

17

Page 18: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

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 e sufficiente sapere che questi non sono altro che processori

multicore, ovvero processori contenenti piu unita di elaborazione le quali sono in

grado di lavorare in parallelo e comunicare tra di loro.

La scelta di una tale soluzione e 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 piu complessi, gli SM hanno dimostrato di essere piu 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 flessibilita rispetto alla soluzione concorrente offerta da AMD.

18

Page 19: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

2Il GPU computing di NVIDIA

In questo capitolo si studia il funzionamento della piattaforma per il GPU com-

puting di NVIDIA, osservandone da vicino il modello di programmazione e l’ar-

chitettura hardware sulla quale di basa.

2.1 L’architettura Tesla

Tesla, schematizzata in Figura 2.1, e il nome dell’architettura che sta alla base

di GPU come la G80 e della piu 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 piu

importanti, mentre ci si soffermera maggiormente su quelle che hanno un ruolo

determinante nell’uso della GPU a fini computazionali.

2.1.1 Il flusso delle operazioni

L’Host Interface e la componente che si occupa della comunicazione tra Host e

Device, ovvero tra il personal computer e la periferica fisicamente connessa a que-

sto, sulla quale risiede la GPU. Fra i suoi compiti principali c’e quello di iniziare

19

Page 20: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Figura 2.1: Architettura della GPU G80 di NVIDIA

20

Page 21: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

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 e 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.

2.1.2 Componenti dello SPA

Lo SPA e composto da diversi Texture Processor Cluster (TPC) (Figura 2.2)

che, in base al livello di performance, possono scalare da uno, nelle GPU di

fascia bassa, fino ad otto o piu nelle GPU di fascia alta. Un TPC contiene un

controller di geometria, uno Streaming Multiprocessor Controller (SMC), due

Stream Multiprocessor ed un’unita di texture.

Stream Multiprocessor

Lo SM (Figura 2.3) e un processore in grado di eseguire istruzioni inerenti sia allo

shading sia al GPU computing. Ogni SM contiene 8 Streaming Processor (SP)

cores, 2 Special Function Unit (SFU), una MultiThreaded instruction fetch and

Issue unit (MT Issue), una cache delle istruzioni, una cache per la memoria

costante, 16KByte di memoria condivisa ed 8192 registri da 32 bit ciascuno. Ogni

SP contiene un’unita scalare Multiply-Add (MAD), dando quindi 8 unita MAD

ad ogni SM. Le due SFU sono usate per il calcolo delle funzioni trascendentali

(esponenziali, logaritmiche, trigonometriche) ed inoltre ogni SFU contiene 4 unita

di moltiplicazione per numeri in virgola mobile. Le operazioni di load e store dalla

memoria principale sono implementante dall’SMC, mentre le operazioni di accesso

alla memoria condivisa, residente sullo SM, sono effettuate direttamente.

21

Page 22: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Figura 2.2: Texture/Processor Cluster nella CPU G80

Figura 2.3: Stream Multiprocessor

22

Page 23: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Unita delle texture

Nel contesto del GPU computing, l’unita delle texture e una componente che per-

mette di accedere in sola lettura della memoria DRAM. Questa e dotata di un

meccanismo di caching1 tale da privilegiare gli accessi localizzati ai dati. Al-

l’interno di un TPC, ogni unita serve contemporaneamente due SM, fornendo

un’alternativa all’uso dello SMC per la lettura dei dati.

Accessi alla memoria

La mancanza di un sistema di caching generale, della memoria DRAM, favorisce

l’ampiezza di banda passante dei dati tra GPU e memoria. Sul chip G80 questa

supera gli 80 GByte/s contro gli 8–10 GByte/s della banda passante fra una

CPU e la sua memoria globale. La mancanza di una cache pero comporta un’alta

latenza per le operazioni sui dati in memoria, superiore di 400-600 volte al tempo

di accesso ai registri locali. Tuttavia grazie ad uno scheduling intelligente delle

operazioni, la latenza di accesso alla DRAM viene nascosta quando si ha un

numero di thread2 per SM sufficientemente alto.

Infatti, quando il numero di thread e abbastanza grande, lo scheduler gestisce le

operazioni da eseguire, in modo da ridurre o eliminare i tempi di inattivita degli

SM. Durante il tempo necessario affinche un’operazione sulla memoria venga

eseguita, lo scheduler mette in attesa il thread che ha richiesto tale operazione.

Si liberano cosı delle risorse, che vengono utilizzate per l’esecuzione del flusso di

istruzioni di un altro thread, rimasto inattivo fino a quel momento.

I tempi di accesso alla memoria condivisa, al contrario di quanto appena visto

per la memoria globale, sono di poco superiori a quelli dei registri. La bassa

latenza e dovuta al fatto che i 16Kbyte di memoria condivisa sono situati all’in-

terno di ogni SM e non esternamente al die (superficie) della GPU. Tuttavia

questi 16Kbyte di memoria condivisa sono partizionati in 16 banchi da 1Kbyte,

ciascuno soggetto a conflitti di accesso da parte degli SP cores componenti l’SM.

Infatti nel caso ci siano piu SP cores ad accedere allo stesso banco, le operazio-

1Un meccanismo di caching e un meccanismo che si appoggia generalmente ad una memoriapiccola e veloce (cache) per ridurre i tempi di accesso alla memoria globale

2Un thread e un flusso di esecuzione all’interno di un’applicazione. Quando il flusso e unicosi parla di applicazione single–thread, altrimenti, quando i flussi sono piu di uno, si parla diapplicazioni multi–thread.

23

Page 24: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

ni di ciascuno di questi vengono serializzate, con un conseguente degrado delle

prestazioni dovuto all’aumento della latenza.

Figura 2.4: Schema degli accessi alla memoria

2.1.3 Un’architettura di tipo SIMT

Mentre al livello implementativo NVIDIA usa delle unita SIMD composte da 8

cores ciascuno, al livello funzionale queste non operano eseguendo 8 volte la stes-

sa istruzione, per un solo flusso di esecuzione, come farebbero delle unita SIMD

tradizionali. Esse, infatti, gestiscono 8 flussi di esecuzione differenti, eseguendo

un’istruzione per ciascuno di essi. In questo modo, i singoli SP cores si compor-

tano come delle unita scalari e non come unita di esecuzione di un processore

vettoriale. Per sottolineare questa differenza, NVIDIA parla di un’architettura

SIMT, ovvero Single Instruction Multiple Threads. A differenza delle unita SIMD,

questa e in grado di massimizzare l’uso delle proprie unita di calcolo, soprattutto

24

Page 25: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

quando l’applicazione e 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 e possibile che, in presenza di un’istruzione condizionale

(if-else, switch-case, ecc. . . ), due o piu 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 e 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, poiche essa introduce diversi problemi

rispetto ad architetture piu semplici. Facendo un confronto con la classe dei

sistemi SIMD e infatti richiesta una maggiore logica di controllo, che comporta un

aumento della superficie del chip e un maggior consumo di energia. E 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.

2.1.4 Operazioni in virgola mobile

L’architettura Tesla contiene un vasto set di istruzioni per operazioni su numeri

interi, numeri in virgola mobile, singoli bit, conversioni di tipo, funzioni tra-

scendentali, controllo di flusso, lettura-scrittura dalla memoria e operazioni sulle

texture.

Di maggiore interesse per il calcolo scientifico sono le istruzioni che operano sui

numeri in virgola mobile, che seguono lo standard di rappresentazione ieee754.

25

Page 26: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

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 puo consultare in [4].

• Moltiplicazione e addizione possono essere combinate in una sola istruzione

(FMAD) che non tiene conto del risultato intermedio della moltiplicazione.

• La divisione e implementata attraverso moltiplicazione per il reciproco.

• L’operazione di radice quadrata e implementata attraverso il reciproco della

radice quadrata stessa.

• La politica di recovery adottata per le condizioni di errore e la store 0.

Nel caso un’operazione incorra in un errore di underflow, questa tecnica

ne azzera il risultato. Al contrario del gradual underflow, adottato nella

maggior parte delle CPU per personal computer, si mantiene cosı invariata la

precisione di macchina. Approfondimenti su entrambe le tecniche si trovano

in [1].

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 pero restano intorno a 2 - 3 ulp.

2.2 CUDA

CUDA e un modello di programmazione parallela ed un ambiente software pro-

gettato per sfruttare le capacita dei nuovi sistemi paralleli multicore e, piu in

particolare, delle GPU ad architettura Tesla. I concetti principali sui quali questa

piattaforma si basa sono:

• Gerarchia dei Thread,

• Memoria condivisa,

• Sincronizzazione a barriera.

26

Page 27: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Questi vengono controllati dal programmatore mediante poche estensioni al lin-

guaggio C, facilitando cosı l’apprendimento per chi ha gia familiarita con questo

ambiente.

Le estensioni indirizzano lo sviluppatore a partizionare un problema in piu

sottoproblemi risolvibili in maniera indipendente. Tali problemi, a loro volta,

vengono risolti mediante la cooperazione di piu thread, ovvero, di piu flussi d’e-

secuzione concorrenti dello stesso programma. Questa soluzione permette sia la

cooperazione fra gruppi di thread per la risoluzione di ogni sottoproblema, sia

l’assegnazione di un sottoproblema indipendente ad uno qualunque dei proces-

sori disponibili. La piattaforma e quindi scalabile rispetto, sia al numero di SM

presenti nel sistema, sia al numero di thread che questi possono gestire.

2.2.1 Gerarchia dei thread

CUDA estende quindi il C permettendo al programmatore di definire delle funzioni

speciali chiamate kernel che, quando invocate, verranno eseguite concorrentemen-

te in N blocchi differenti ciascuno composto da M thread, come rappresentato in

Figura 2.5.

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 e 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 piu 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 e data dal primo dei tre valori di

threadIdx. Durante l’esecuzione, per ogni thread, questo valore sara distinto e

compreso tra 0 e M-1.

Listato 2.1: vecAdd

global void vecAdd(float∗ A, float∗ B, float∗ C){int i = threadIdx.x; //prima componente della variabile threadIdx

C[i] = A[i] + B[i];

27

Page 28: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Figura 2.5: Gerarchia dei thread in CUDA.

}host int main(){

vecAdd<<<1, M>>>(A, B, C);

}

Nell’esempio sono dichiarate due funzioni. All’interno della funzione main, il

punto di entrata, si invoca la funzione vecAdd, la quale riceve in input 3 riferimenti

a vettori. Lo specificatore global e ed i parametri contenuti tra i caratteri

<<< e >>>, indicano rispettivamente al compilatore che la funzione vecAdd

verra eseguita sulla GPU in un solo blocco composto da M thread concorrenti.

Analogamente a quanto detto per threadIdx, si ha che ai thread appartenenti

allo stesso blocco e assegnata la variabile blockIdx, accessibile all’interno del

28

Page 29: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

kernel e contenente il numero di blocco a cui appartiene il thread stesso. Questa

variabile e composta da una o due componenti, a seconda che i blocchi siano

organizzati logicamente in uno spazio monodimensionale o bidimensionale.

Il codice di esempio 2.2 estende il codice 2.1, introducendo l’uso di blocchi

organizzati in uno spazio monodimensionale.

Listato 2.2: vecAdd

global void vecAdd(float∗ A, float∗ B, float∗ C){int i = blockDim.x∗blockIdx.x+threadIdx.x; //calcolo dell’id assoluto

C[i] = A[i] + B[i];

}int main(){

vecAdd<<<N, M>>>(A, B, C);

}

In questo esempio la dimensione dei vettori A e B e di M ×N elementi, ed i para-

metri contenuti tra i caratteri <<< e >>> indicano al compilatore che verranno

eseguiti N blocchi composti da M thread ciascuno. Al fine di assegnare a tutti

i thread la somma di una componente univoca dei due vettori, l’assegnazione di

tale componente non dipendera piu solo dalla variabile threadIdx, ma dipendera

anche dalla variabile blockIdx. In questo modo gli M thread appartenenti agli N

blocchi differenti, sono in grado di sommare distintamente tutti gli elementi dei

vettori A e B.

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, e 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 e 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

pero 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

Page 30: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

blocchi. Per calcolare l’efficienza di una configurazione si introduce il concetto di

occupazione, il quale e facilmente esprimibile come il rapporto tra il numero di

warps attivi e il numero massimo di warps gestibili da un SM,

occupacy =active warps

max active warps.

Configurazioni che offrono l’occupazione massima possono essere facilmente co-

struite. Le piu efficienti per le GPU della serie G80 sono quelle che hanno almeno

192 thread per blocco. In questo modo lo scheduler e in grado di mascherare

buona parte della latenza di lettura e scrittura, sia della memoria, sia dei registri.

Configurazioni con molti thread per blocco, pero, introducono delle limitazioni

sul numero dei registri utilizzabili all’interno di un kernel, ad esempio: se si de-

cide per una configurazione di 256 thread per blocco, scrivere un kernel che usa

10 registri invece di 11 permette di passare da 2 a 3 blocchi (da 16 a 24 warps)

attivi contemporaneamente per ogni singolo SM. Infatti, considerando 3 blocchi

in esecuzione per ogni SM, nel caso ogni thread usi 10 registri, i registri utilizzati

in totale sono 3×256×10 = 7680; un valore entro il limite degli 8192 disponibili.

Invece, considerando 3 blocchi per ogni SM, ma assumendo che ogni thread usi

11 registri, questa volta i registri utilizzati in totale sono 3 × 256 × 11 = 8448,

ovvero troppi affinche 3 blocchi possano essere attivi contemporaneamente su un

solo SM.

2.2.2 Gerarchia della memoria

Ogni thread ha accesso, in lettura e scrittura, a piu spazi di memoria durante la

sua esecuzione:

Registri Su ogni SM sono allocati un certo numero di registri e, ad ogni thread, ne

e concesso solo un numero limitato. Le operazioni sui registri hanno latenza

praticamente nulla, anche se possono avere dei ritardi di lettura-dopo-una-

scrittura dovuti all’aggiornamento del loro contenuto. Il numero di registri

disponibili per ogni thread varia in base alla configurazione di esecuzione

che si sceglie. Fissata quest’ultima, il il loro numero puo essere calcolato

mediante la formulaR

B × ceil(T, 32),

30

Page 31: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Figura 2.6: Gerarchia della memoria in CUDA.

dove R e il numero di registri per ogni SM, B e il numero di blocchi attivi

per SM e T e il numero di thread per blocco, che, come si vede, viene

approssimato per eccesso con il piu piccolo multiplo di 32.

Memoria globale Questa e la memoria che i thread utilizzano, sia per leggere i

dati da elaborare, sia per scrivere i risultati delle loro operazioni. Essa e

implementata nella DRAM, quindi i suoi tempi di accesso, come gia visto

durante lo studio dell’architettura Tesla, sono centinaia di volte superiori a

quelli dei registri: e importante ridurne l’utilizzo il piu possibile.

Quando l’accesso alla memoria globale non e evitabile, e conveniente usare

delle tecniche per leggere o scrivere i dati in blocchi (accessi coalesced).

Nelle GPU della serie G80, un’operazione di lettura o scrittura coalesced si

ha quando almeno la meta dei thread di un warp (half-warp) effettua un

accesso alla memoria, in modo tale che il thread i-esimo acceda alla locazione

31

Page 32: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

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.

Memoria locale privata In questa memoria vengono allocate le variabili locali

dei singoli thread, che non possono essere contenute nei registri. Anche que-

sto e uno spazio di memoria implementato nella DRAM e, di conseguenza,

i tempi di accesso sono gli stessi della memoria globale.

Memoria condivisa Questa memoria e accessibile a tutti i thread dello stesso

blocco e, generalmente, e utilizzata per condividere i risultati intermedi dei

processi di calcolo. E implementata mediante i banchi di memoria condivisa

contenuti all’interno di ogni SM, quindi la latenza e leggermente maggiore

di quella dei registri. Spazi contigui di memoria condivisa sono allocati su

banchi differenti in modo da ridurre i conflitti di accesso e, di conseguenza,

anche i tempi di latenza.

I seguenti sono spazi di memoria accessibili in sola lettura.

Memoria costante E uno spazio di memoria implementato nella memoria glo-

bale, e contiene quei valori che restano costanti durante tutta l’esecuzione

del kernel. I tempi di latenza sono al pari di quanto visto per la memoria

globale; tuttavia questo spazio di memoria dispone di una cache. La sua

presenza riduce drasticamente i tempi di attesa nel caso si acceda molte

volte allo stesso elemento.

Memoria delle texture Questo spazio di memoria e accessibile attraverso l’unita

delle texture. Rappresenta quindi un’interfaccia di sola lettura alla memoria

globale, che offre un meccanismo di caching ottimizzato per la localita bidi-

mensionale e, facoltativamente, un diverso tipo di indirizzamento ai dati3.

Nonostante le latenze siano superiori agli accessi di tipo coalesced verso la

memoria globale, l’uso della memoria delle texture puo essere molto van-

taggioso. Tramite questo spazio di memoria infatti, si possono eseguire

operazioni di lettura che, o non possono essere rese coalesced, o coinvolgono

3Ulteriori informazioni sulla memoria delle texture si trovano in [4], alla quale e stata dedicataun’intera appendice.

32

Page 33: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

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 e

l’unico meccanismo di sincronizzazione offerto da CUDA.

La mancanza di una primitiva di sincronizzazione globale costringe ad attendere

il ritorno della chiamata ad un kernel, per avere la certezza che tutti i blocchi

abbiano terminato il proprio compito. Tuttavia, gia dalla seconda versione della

GPU G80, e stata prevista la possibilita di effettuare operazioni atomiche sulla

memoria globale, dando quindi agli sviluppatori la possibilita di implementare

primitive a semaforo per la sincronizzazione globale dei thread.

2.2.4 Compute capability

In questo capitolo si sono osservate, sia tutte le caratteristiche dell’ambiente

CUDA, sia i limiti che vi impone la GPU G80. Ovvero i limiti sulla configu-

razione di esecuzione dei kernel, il numero di registri disponibili, l’uso di funzione

atomiche, ecc. . . Questi limiti non sono gli stessi per ogni GPU, pertanto NVIDIA

assegna un valore chiamato compute capability, al quale sono associati i limiti e le

caratteristiche generali della GPU in questione. La lista completa delle compute

capability di ogni GPU e consultabile in [4].

33

Page 34: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

2.2.5 Uso della piattaforma

Prima di terminare questa sezione, e importante sottolineare come CUDA sia

stato concepito per sfruttare le capacita di calcolo parallelo delle GPU come se

queste fossero dei coprocessori della CPU principale. Pertanto l’Host si occupa sia

dei trasferimenti dei dati da e verso la DRAM del Device, sia della configurazione

e dell’esecuzione dei kernels. Un esempio del flusso di un’applicazione CUDA e

mostrato in Figura 2.7, dove si osserva l’alternanza tra il codice seriale eseguito

sull’Host, e l’esecuzione parallela di uno o piu kernel sul Device.

Figura 2.7: Flusso delle operazioni per un’applicazione costruita con CUDA

34

Page 35: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

3Il Lavoro di Implementazione

In questo terzo capitolo si mostrano le fasi di sviluppo della componente soft-

ware realizzata per questa tesi. Si illustreranno le motivazioni che hanno spinto

ad un’implementazione per l’ambiente CUDA, gli strumenti di programmazione

utilizzati ed infine le fasi di sviluppo del codice.

3.1 Studio iniziale

Il software che si intende portare su questa piattaforma, come gia accennato nel-

l’introduzione, e una implementazione ad-hoc del Metodo dei Gradienti Coniugati

per matrici pentadiagonali simmetriche e definite positive (sdp). Questo e 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 e eseguito contemporaneamente da migliaia di thread.

3.1.1 L’ambiente Matlab

Il sorgente originale e stato scritto in linguaggio Matlab e, pertanto, la sua ese-

cuzione avviene per mezzo di un interprete. Quando un software e interpretato

35

Page 36: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

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 pero ad una parte delle

prestazioni.

Per confrontare i vantaggi che il modello CUDA offre rispetto al modello di

programmazione classico, prima di tutto si e reso necessario riscrivere il software

originale. Si e utilizzato cosı, un linguaggio che, una volta compilato, producesse

un programma nativo per la piattaforma dell’Host. Fortunatamente Matlab ha

la facolta di integrarsi con software esterni, scritti in linguaggi compilabili. In

questo modo all’interno dello stesso ambiente e stato possibile confrontare le tre

implementazioni: quella scritta in linguaggio interpretato Matlab, quella scritta

in linguaggio compilabile C per la CPU, e quella scritta in linguaggio C esteso

dall’ambiente CUDA.

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[ ]).

Questo metodo rappresenta il punto di entrata del programma, ed i parametri

della sua signature2 sono:

nlhs Variabile di tipo intero contenente il numero di parametri di output richiesti

alla chiamata del metodo dall’ambiente Matlab.

plhs Un array di lunghezza nlhs contenente i riferimenti di tipo mxArray ai valori

di output del metodo invocato.

nrhs Variabile di tipo intero contenente il numero dei parametri di input passati

alla chiamata del metodo dall’ambiente Matlab.

prhs Un array di lunghezza nrhs contenente i riferimenti di tipo mxArray ai

parametri presenti nella firma del metodo invocato.

1Un’istruzione nativa e un’istruzione che viene decodificata ed eseguita direttamente dallaCPU, senza nessun passaggio intermedio.

2La signature di un metodo e l’insieme di informazioni che identificano il metodo stesso, inquesto caso: il suo nome, il valore restituito ed i parametri che questo riceve.

36

Page 37: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

mxArray e 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 a2,1 . . . am−1,n am,n

m

n

=:

a1,1 a1,2 . . . a1,n

a2,1 a2,2 . . . a2,n

......

. . ....

am,1 am,2 . . . am,n

L’allocazione e l’accesso a questa struttura avviene mediante l’uso di funzioni spe-

cifiche, dichiarate negli headers forniti. Assieme a queste sono disponibili anche

diversi metodi analoghi a quelli della libreria standard del C per l’Input/Output

nello spazio di lavoro, che facilitano lo sviluppo e il testing del codice. E possibile

consultare la documentazione completa dei tipi di dato e delle funzioni offerte

dall’ambiente Matlab, in [19] e in [17].

La compilazione di sorgenti in linguaggio C avviene mediante il comando mex,

mentre per la compilazione di sorgenti scritti utilizzando la piattaforma CUDA,

NVIDIA fornisce un proprio script, nvmex, il cui funzionamento e analogo al quello

del precedente comando. Si possono cosı integrare programmi che verranno ese-

guiti nativamente sull’Host e programmi che, oltre a quest’ultima caratteristica,

sfruttano le capacita di calcolo del Device.

Si termina quindi questa sottosezione con tre esempi. Questi mostrano come

l’operazione di elevazione al quadrato degli elementi di un vettore puo essere

integrata in Matlab; prima con linguaggio Matlab poi con linguaggio C ed infine

in ambiente CUDA.

Matlab Il linguaggio Matlab, grazie alla sua flessibilita consente di eseguire l’o-

perazione con una sola riga di codice senza doversi preoccupare della com-

pilazione.

Listato 3.1: quadrato.m

function x=quadrato(r)

x=r.ˆ2;

37

Page 38: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

C L’integrazione con il linguaggio C richiede, sia una gestione piu 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, e

necessario compilare il sorgente con il comando mex cquadrato.c.

Listato 3.2: cquadrato.c

#include ”mex.h”

void mexFunction( int nlhs, mxArray ∗plhs[], int nrhs, const mxArray ∗prhs[] )

{int i=0;

int m = mxGetM(prhs[0]); /∗Numero di Righe∗/int n = mxGetN(prhs[0]); /∗Numero di Colonne∗/const int len=m∗n;

plhs[0]=mxCreateDoubleMatrix(m, n,mxREAL);/∗Alloco memoria per l’output∗/

double∗ output=mxGetPr(plhs[0]); /∗Recupero l’indirizzo di memoria∗/double∗ input=mxGetPr(prhs[0]); /∗nel quale sono contenuti i valori∗/

for(i=0;i<len;i++)

output[i]=input[i]∗input[i]; /∗elevo al quadrato∗/}

CUDA L’implementazione in ambiente CUDA, oltre a tutti gli accorgimenti visti

per l’esempio precedente, richiede che i parametri in input vengano copiati

dalla memoria dell’Host a quella del Device, e viceversa per parametri di

output. Inoltre e necessaria la scrittura di una funzione separata che assume

il ruolo di kernel per l’esecuzione sul Device. La compilazione in questo ca-

so avviene mediante il comando ../bin/nvmex -O -f ../bin/nvopts.sh

-L/usr/local/cuda/lib/ -lcufft -Wl cuquadrato.cu

Listato 3.3: cuquadrato.cu

#include ”mex.h”

global void dsquare(float∗ d, int len)

{int id=threadIdx.x+blockIdx.x∗blockDim.x;

const int jmp=blockDim.x∗gridDim.x;

for (; id<len;id+=jmp)

d[id]∗=d[id];

}void mexFunction( int nlhs, mxArray ∗plhs[], int nrhs, const mxArray ∗prhs[] )

{int m = mxGetM(prhs[0]); /∗Numero di Righe∗/int n = mxGetN(prhs[0]); /∗Numero di Colonne∗/const int len=m∗n;

plhs[0]=mxCreateNumericMatrix(m, n,mxSINGLE CLASS,mxREAL);

38

Page 39: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

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);

/∗1000 blocchi per 64 threads ciascuno∗/dsquare<<<1000,64>>>(device,len);

cudaMemcpy(output,device,len∗sizeof(float),cudaMemcpyDeviceToHost);

cudaFree(device);

}

3.1.2 L’implementazione Matlab e le strutture dati

Il Listato 3.4 e il corpo completo dell’implementazione Matlab sulla quale si e

lavorato. Si divide in due funzioni: la principale, cgmamma, e la funzione che si

invoca per avviare il metodo dei gradienti coniugati, mentre la seconda, matvec,

implementa il prodotto matrice–vettore per matrici pentadiagonali sdp.

Listato 3.4: cgmamma.m

function [x,itcg,eta,err] = cgmamma( mask, etan, Hdiag, Hipm12j, ...

Hijpm12, b, tol, maxit, zero )

%

% PCG con scalamento diagonale simmetrico

%

DT = sqrt( Hdiag + (Hdiag<=zero)/eps );

D1 = Hipm12j./( DT(1:end−1,:).∗DT(2:end,:) );

D2 = Hijpm12./( DT(:,1:end−1).∗DT(:,2:end) );

r = b −matvec( Hdiag, Hipm12j, Hijpm12, etan );

r = mask.∗r./DT; x = zeros(size(etan));

p = r; eta = sum(sum( r.∗r )); itcg = 0; eta00 = eta; err = 0;

while eta>tol & itcg<maxit

itcg = itcg + 1; Ap = matvec( [], D1, D2, p, 1 ).∗mask;

d = sum(sum( p.∗Ap )); lam = eta/d; x = x + lam∗p; r = r − lam∗Ap;

eta0 = eta; eta = sum(sum( r.∗r )); mu = eta/eta0; p = r + mu∗p;

if eta<10∗tol & eta0<=eta, break, end %% stagnazione non grave

if eta>1e3∗eta00 & eta00<1e2∗tol, x = 0;

disp( ’CGMAMMA stagnation: x set to 0’), break, end

end

if itcg==maxit & eta>tol

err = 1; disp( ’CGMAMMA exception:’)

disp( sprintf ( ’ initial and final residual = %g, %g’, eta00,eta))

else

x = etan + x./DT;

end

return

39

Page 40: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

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

Questa implementazione ad hoc del Metodo dei Gradienti Coniugati, adopera

delle matrici come struttura per la rappresentazione dei vettori, al fine di adattarsi

meglio alla natura del problema nel quale e impiegato il metodo. Date delle

strutture a matrice A, B, C, D ed X corrispondenti rispettivamente ai parametri

in ingresso Hdiag, Hipm12j, Hijpm12, b ed etan, queste rappresentano i dati del

seguente sistema lineare:

a1,1 b1,1 0 . . . c1,1 0 . . . 0

b1,1 a2,1. . . . . .

...

0. . . . . . bm−1,1

. . ....

... bm−1,1 am,1 0 cm,n−1

c1,1 0 a1,2 b1,2 0

0. . . b1,2

. . . . . ....

.... . . . . . am−1,n bm−1,n

0 . . . . . . cm,n−1 0 . . . bm−1,n am,n

x1,1

x2,1

...

xm,1

x1,2

...

xm−1,n

xm,n

=

d1,1

d2,1

...

dm,1

d1,2

...

dm−1,n

dm,n

(3.1)

Le dimensioni delle matrici in ingresso sono di conseguenza

• Hdiag = m× n

• Hipm12j = (m− 1)× n

• Hijpm12 = m× (n− 1)

• b = m× n

• etan = m× n

40

Page 41: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

A questi parametri, se ne aggiunge uno: il parametro mask. Questo parametro

rappresenta un vettore composto da elementi unitari o nulli. Il suo scopo e 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.

3.1.3 Legge di Amdhal

Uno degli interessi del lavoro di implementazione, e quello di osservare i vantaggi

prestazionali che si possono trarre dall’ambiente CUDA. Al fine di stimare l’in-

cremento di prestazioni ottenibile, prima di procedere oltre, si introduce la legge

di Amdhal.

La legge Amdahl e stata formulata nel 1967 da G.Amdahl nel tentativo di

valutare l’incremento di prestazioni ottenibile da un sistema multiprocessore e,

piu generalmente, l’incremento di prestazioni di un sistema in base all’aumento

delle performance di una sua componente. Se T e il tempo di esecuzione di un

dato programma, tale tempo puo essere diviso in un tempo Tc ed in un tempo

Tnc, rispettivamente il tempo di utilizzo di una certa componente e il tempo in

cui opera il resto del sistema

T = Tc + Tnc.

Quando la componente viene sostituita con una piu “performante”, il tempo

di esecuzione per quest’ultima diventa T ′c < Tc e il tempo totale si trasforma

diventando

T = T ′c + Tnc.

Definendo l’incremento di prestazioni (speed up o accelerazione) come il rapporto

tra il tempo di esecuzione precedente e quello successivo alla modifica, lo speed

up della componente e

Sc =Tc

T ′c

.

Data fc =Tc

T, frazione di utilizzo della componente all’interno del sistema origina-

rio, si ha che lo speed up della componente Tc, si riflette sullo speed up dell’intero

41

Page 42: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Figura 3.1: Effetto della legge di Amdhal.

sistema secondo il rapporto

S =1

(1− fc) +fc

Sc

.

Questo dimostra che l’aumento delle prestazioni di un sistema e limitato supe-

riormente dalla frazione di utilizzo della componente che si accelera (Figura 3.1).

Infatti si ha che

Smax = limSc→∞

1

(1− fc) +fc

Sc

=1

1− fc

.

3.1.4 Prima analisi delle prestazioni

Alla luce della legge di Amdhal, per conoscere quali fossero le operazioni piu 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, e 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 e stata invocata, o

quanto tempo e stato speso all’interno di questa. Con queste informazioni si e 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.

Da questa analisi risulta che, la function relativa al prodotto matrice–vettore,

e quella ad avere la maggior frazione di utilizzo. Come primo approccio si e

quindi cercato di lavorare solamente sulla funzione matvec, nonostante lo speed

42

Page 43: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Linea Codice Chiamate Tempo (s) Tempo %16 Ap=matvec([],D1,D2,p,1).*mask; 443 11.188 74.117 d=sum(sum(p.*Ap)); 443 1.101 7.319 x=x+lam*p; 443 0.852 5.622 eta=sum(sum(r.*r)); 443 0.829 5.520 r=r-lam*Ap; 443 0.521 3.4

Altre ... 0.616 4.1Totale 15.107 100

Tabella 3.1: Informazioni prodotte dal profiler di Matlab sull’esecuzione delmetodo cgmamma.

up massimo raggiungibile per cgmamma fosse di solo

1

1− 0.741= 3, 86.

3.2 Prima implementazione

In questa prima parte si lavora unicamente sulla function matvec, realizzandone

due implementazioni alternative: la prima, cmatvec, eseguita dall’Host, e la se-

conda, nvmatvec, eseguita dal Device. L’implementazione di queste due function

ha pero un significato principalmente didattico. Questo perche la loro applica-

bilita all’interno dell’ambiente Matlab e molto limitata dagli overhead (costi di

gestione), dovuti alle chiamate di funzione esterne. Come si vedra, sostituendo

alla function matvec una delle due nuove implementazioni, si ha che buona parte

dei guadagni prestazionali, vengono persi in overhead.

Si presenta inoltre un altro problema, riguardante l’uso della function nvmatvec.

Mentre l’implementazione per l’Host accede ed opera sui dati gia salvati nella me-

moria principale, l’implementazione per il Device necessita che gli stessi dati ven-

gano prima copiati nella memoria DRAM della GPU, elaborati, e infine trasferiti

nuovamente in memoria principale dell’Host. Dato che la funzione di prodotto

matrice–vettore viene richiamata ad ogni iterazione del Metodo dei Gradienti Co-

niugati, l’uso della function nvmatvec, sarebbe sconveniente non solo per i costi

di gestione della chiamata. Per ogni passo iterativo, sarebbe infatti necessario

copiare tutti i dati dalla memoria principale dell’Host alla DRAM del Device,

alzando ulteriormente gli overhead.

43

Page 44: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Quando si trattera dell’implementazione per il Device dell’intero Metodo dei

Gradienti Coniugati, si vedra come l’ideale consista nell’allocare i dati in memoria

soltanto una volta. Questa soluzione e applicabile quando almeno il ciclo princi-

pale del Metodo dei Gradienti Coniugati e 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. E importante specificare che i

tempi di esecuzione sono riferiti all’hardware della macchina su cui si e 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.

3.2.1 Implementazione Host: cmatvec

L’implementazione Host (listato ??) e costituita da due funzioni, di cui una co-

stituisce il punto di entrata della function, mentre l’altra e 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.

1. Il primo ciclo effettua la moltiplicazione membro a membro della diagonale

principale hdiag, di dimensioni m× n, con il vettore p (Figura 3.2).

2. Il secondo ciclo svolge le due moltiplicazioni con il vettore hijpm12, di

dimensioni m×(n−1), rappresentante la sottodiagonale e la sopradiagonale

44

Page 45: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Figura 3.2: Moltiplicazione conla diagonale princi-pale

Figura 3.3: Moltiplicazione conle sottodiagonali piuesterne

Figura 3.4: Moltiplicazione con le prime sotto-diagonali

45

Page 46: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

piu 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).

3. Nel terzo ciclo, infine, si effettuano le moltiplicazioni con il vettore hipm12j,

di dimensione (m− 1)× n, rappresentante sia la prima sottodiagonale che

la prima sopradiagonale della matrice. Queste moltiplicazioni non si pos-

sono eseguire semplicemente applicando un offset (scostamento), come nel

caso precedente. Questo perche, gli elementi della sottodiagonale del siste-

ma (3.1) non sono interamente memorizzati nella corrispondente struttura

dati. Infatti, il vettore hipm12j non contiene gli elementi nulli di tale sot-

todiagonale, rompendo cosı la diretta corrispondenza fra gli indici di questo

vettore e gli indici del vettore p. Le moltiplicazioni effettuate in questo

ciclo hanno quindi richiesto la scrittura di una funzione capace di associare

ad ogni indice del vettore hipm12j il corrispondente indice del vettore p

(Figura 3.4).

Eseguire il prodotto matrice–vettore mediante tre cicli separati, invece di usare

un unico ciclo, puo non sembrare la scelta migliore. Nella pratica invece non e

cosı, rivelando che i meccanismi di caching della CPU sono piu efficienti quando

si lavora con un piccolo numero di vettori. In questo modo, essendo la cache una

memoria di dimensione abbastanza limitata, un numero maggiore di elementi

per ogni vettore puo essere trasferito in quest’ultima, riducendo cosı gli accessi

alla memoria principale. Adoperando un solo ciclo, e quindi, accedendo ad ogni

iterazione a tutti i vettori, si provoca la riduzione del numero di elementi per

ogni vettore presenti nella memoria cache. Causando un accesso piu frequente

alla memoria principale, con un conseguente degrado delle performance.

Si e effettuato un confronto fra le prestazioni di questo codice e la versione

scritta in linguaggio Matlab, ottenendo accelerazioni significative dovute prin-

cipalmente all’esecuzione nativa delle istruzioni; in tabella 3.2 sono riportati i

risultati.

3.2.2 Implementazione CUDA: nvmatvec

L’implementazione CUDA (listato ??) del metodo matvec e composta da quattro

funzioni: il punto di entrata, la funzione di preparazione del Device, il kernel e

46

Page 47: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

la funzione di lettura dei risultati. Dato che il punto di entrata e sostanzialmente

identico a quello presentato nell’implementazione Host, ci si sofferma sulle altre

tre funzioni.

La prima ad essere invocata e la funzione di preparazione del Device, la quale

si occupa di copiare i dati dall’Host alla memoria del Device, dopo averne al-

locata la quantita necessaria. In questa stessa funzione viene impostata l’unita

delle texture che servira per leggere quei dati il cui accesso e inefficiente a causa

di un allineamento non favorevole. Infatti, come si e gia visto, l’accesso diretto

alla memoria globale ha alti tempi di latenza, e per ridurre questo svantaggio e

necessario effettuare il piu spesso possibile accessi di tipo coalesced. Questo tipo

di accesso pero puo essere effettuato solo per la moltiplicazione della diagonale

principale, nella quale l’i-esimo elemento di v e determinato dalle componenti

i-esime dei vettori p e hdiag. Per la moltiplicazione delle altre due diagonali, il

calcolo dell’elemento i-esimo del vettore v coinvolge l’accesso a locazioni dei vet-

tori hijpm12 e hipm12j aventi indici non corrispondenti alle locazioni del vettore

p. A causa di questo non si possono effettuare accessi di tipo coalesced. Tut-

tavia le aree di memoria a cui si accede, hanno una certa localita, ad esempio:

nella moltiplicazione della seconda sottodiagonale, l’i-esimo elemento del vettore

v coinvolgera l’i-esimo elemento di hijpm12 e l’i + m-esimo elemento di p. In

questo caso i meccanismi di caching della memoria delle texture offrono un’alter-

nativa efficiente all’accesso diretto alla memoria globale, riducendo drasticamente

il tempo impiegato per la lettura di elementi non allineati.

La seconda funzione istanzia un certo numero di thread che eseguiranno il kernel

sul Device. Il kernel svolge le operazioni di moltiplicazione delle diagonali in modo

tale che, data una configurazione di M blocchi composti da N thread ciascuno, il

j-esimo thread del k-esimo blocco, valuti il vettore v alle locazioni i tali che

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’unita delle texture.

In Tabella 3.2 si riportano i risultati di test analoghi a quelli effettuati per

l’implementazione Host. Anche in questo caso si registrano degli speed up nei

47

Page 48: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

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 puo competere con una CPU di fascia

superiore.

Test m× n matvec(s) cmatvec(s) nvmatvec(s)Device Host

speed-up speed-up1 200× 200 17,98 6,40 5,3 3,39 2,812 300× 300 49,27 17,54 13,08 3,76 3,393 400× 400 122,58 27,43 21,10 5,80 4,464 500× 500 202,41 44,00 35,51 5,70 4,605 600× 600 310,00 66,69 49,48 6,26 4,641 100× 400 18,67 6,04 5,80 3,21 2,912 100× 900 59,59 15,02 11,57 5,15 3,963 100× 1600 125,63 28,90 21,76 5,77 4,344 100× 2500 210,45 46,78 32,72 6,41 4,495 100× 3600 320,78 71,55 47,82 6,69 4,481 400× 100 17,82 6,49 5,6 3,18 2,742 900× 100 53,83 14,63 13,06 4,12 3,673 1600× 100 119,94 27,6 22,01 5,40 4,344 2500× 100 202,51 44,15 35,81 5,65 4,585 3600× 100 309,95 66,97 49,48 6,26 4,62

Tabella 3.2: Confronto dei i tempi di esecuzione, fra le implementazioni Matlab,Host e Device della funzione matvec. Le ultime due colonne mostranogli speed up delle function sviluppate, rispetto all’implementazioneoriginale. A numero di test uguale corrisponde un egual numero dielementi

3.3 Seconda Implementazione

Rispetto alla precedente implementazione, in questa nuova sezione si affronta il

problema dell’allineamento dei dati. Questi, come si e visto negli schemi in Figura

3.3 e 3.4, hanno un allineamento sfavorevole, soprattutto per quanto riguarda la

moltiplicazione della prima sottodiagonale. Infatti, per ogni locazione del vettore

risultante v che si va ad aggiornare, si deve effettuare una serie di calcoli per

ricavare l’indice della corrispondente locazione del vettore hipm12j. Decidendo di

ripensare le strutture dati per favorire l’allineamento dei vettori si puo ottenere

un guadagno sensibile di prestazioni rinunciando pero al massimo risparmio di

memoria.

48

Page 49: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

L’idea, in questa seconda implementazione, consiste infatti nel ridurre la com-

plessita 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 piu 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.

Figura 3.5: Nuovo schema di moltiplicazione per la prima sottodiagonale

L’aggiunta degli elementi nulli, e un’operazione resa immediata dalla flessibi-

lita dell’ambiente Matlab che, con una sola istruzione, consente di costruire la

struttura dati di cui si necessita. Nelle due seguenti implementazioni si assumera

quindi che al posto della matrice hipm12j di dimensioni (m− 1)× n la funzione

riceva una matrice di dimensioni m×n, costruita dalla precedente con la seguente

istruzione:

ns = [hipm12j; zeros(1, length(hipm12j))]; :

hdiag

0 0 · · · 0 0

.

49

Page 50: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

L’uso di questa nuova struttura dati non si e 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.

3.3.1 Implementazione Host

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.

In Tabella 3.3 si riportano i risultati dei test eseguiti su quest’ultima implemen-

tazione. Si nota immediatamente l’aumento dell’accelerazione dovuto a questo

nuovo schema di allineamento.

Test m× n matvec(s) nvmatvec(s) cmatvec(s)Device Host

speed-up speed-up1 200× 200 17,98 3,92 3,47 4,58 5,182 300× 300 49,27 9,32 13,29 5,28 3,713 400× 400 122,58 15,64 23,83 7,83 5,144 500× 500 202,41 24,93 37,72 8,12 5,375 600× 600 310,00 33,96 57,38 9,13 5,401 100× 400 18,67 4,41 3,49 4,23 5,342 100× 900 59,59 8,70 7,95 6,84 7,493 100× 1600 125,63 14,76 21,44 8,51 5,854 100× 2500 210,45 23,31 37,95 9,02 5,545 100× 3600 320,78 32,12 57,62 9,98 5,561 400× 100 17,82 4,80 5,32 3,71 3,342 900× 100 53,83 8,85 7,99 6,08 6,733 1600× 100 119,94 16.01 23,80 7,49 5,034 2500× 100 202,51 25,39 38,07 7,97 5,315 3600× 100 309,95 34,62 58,02 8,95 5,34

Tabella 3.3: Confronto dell’implementazione matvec di Matlab con le funzioniHost e Device sfruttanti la nuova struttura dati. A numero di testuguale corrisponde un egual numero di elementi

50

Page 51: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

3.3.2 Implementazione Device

La seconda versione dell’implementazione per il Device non modifica solo l’allinea-

mento dei vettori per la moltiplicazione della prima sottodiagonale, ma introduce

anche l’uso della memoria condivisa per ridurre gli accessi dalla memoria globale.

Come si e gia visto, la memoria condivisa permette ai thread dello stesso blocco

di scambiare i propri dati fra di loro.

Con la nuova struttura dati, la moltiplicazione del vettore hipm12j con il vet-

tore p e 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,

salvera i valori di p[i] e hipm12j[i]. Ogni thread in questo modo potra 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.

In Tabella 3.3 si riportano i risultati dei test effettuati, i quali mostrano un

netto speed up rispetto alla versione precedente, soprattutto quando si lavora su

matrici di grandi dimensioni.

3.4 Implementazione Finale

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.

In questa sezione, quindi, si osservera la realizzazione di due metodi che potran-

no essere richiamati dall’ambiente Matlab, e che eseguiranno al suo interno tutte

le operazioni e le iterazioni per la risoluzione del sistema (3.1). Entrambe le imple-

mentazioni provvederanno percio a recuperare i parametri ricevuti in input dalla

chiamata dell’ambiente Matlab e, senza nessun overhead esterno, svolgeranno le

stesse operazioni del metodo cgmamma.

51

Page 52: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Per far questo, oltre al metodo matvec, e 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.

3.4.1 Implementazione Host: ccgmamma

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 e stato possibile, e stata utilizzata la tecnica di unrolling

dei cicli, affinche il compilatore potesse utilizzare le istruzioni vettoriali presenti

nella CPU; questo ovviamente per migliorare le prestazioni.

3.4.2 Implementazione Device: nvcgmamma

Per l’implementazione Device, a differenza di quanto si puo osservare per l’Host,

all’interno del ciclo principale non c’e una diretta corrispondenza fra chiamate di

funzioni e operazioni svolte. Consultando il listato ??, si osserva infatti che il

ciclo principale del metodo e composto di sole tre invocazioni di funzioni kernel.

Queste svolgono al loro interno le operazioni dell’algoritmo, raggruppandole in

modo da ridurre sia l’overhead di creazione dei thread, sia i trasferimenti dei dati

dalla memoria globale ai registri della GPU. La divisione delle operazioni e la

seguente:

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 gia salvati nei registri, si calcola il loro prodotto scalare, implementando

cosı le istruzioni matvec([],D1,D2,p).*mask e d=sum(sum(p.*Ap)).

parte 2 Si esegue l’operazione saxpy (Scalar Alpha X Plus Y ) corrispondente

all’istruzione r=r-lam*Ap dell’algoritmo Matlab e, sfruttando gli elemen-

ti gia salvati nei registri, si calcola il prodotto scalare tra r e se stesso,

corrispondente all’istruzione eta=sum(sum(r.*r)).

52

Page 53: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

parte 3 Si eseguono le operazioni x=x+lam*p e p=r+mu*p, le quali sono state

raggruppate nello stesso kernel perche entrambe accedono agli elementi del

vettore p; in questo modo si riducono gli accessi alla memoria globale.

Un’osservazione particolare va alle operazioni appena citate di prodotto scalare e

calcolo della norma, per le quali si e adoperata la tecnica di Parallel Reduction.

Questa, in O(log2 M) passi, permette di eseguire operazioni caratterizzate da avere

come risultato uno scalare, dipendente da tutti gli elementi di un vettore.

Dati N blocchi composti da M=2n thread ciascuno, il funzionamento di questa

tecnica prevede che al primo passo tutti gli M thread di ogni blocco siano attivi, e

che ciascuno di questi legga un certo numero degli elementi del vettore. Terminata

la lettura, ogni thread memorizza il risultato (risultato parziale) delle operazioni

fra gli elementi letti, nella locazione in memoria condivisa che gli corrisponde.

Nei passi successivi i thread attivi per ogni blocco vengono dimezzati, lasciando

a questi il compito di leggere ed aggiungere al proprio risultato parziale, quello

dei thread non piu attivi al passo corrente (Figura 3.6).

La procedura termina quando resta attivo un solo thread per blocco, il quale si

occupera di scrivere il proprio risultato parziale nella memoria globale. Si ottiene

cosı un nuovo vettore composto da soli N elementi (1 per ogni blocco) per il quale

o si utilizza nuovamente la tecnica di Parallel Reduction per ridurne ancora la

dimensione o, nel caso N sia abbastanza piccolo, si procede scorrendo il vettore e

operando su ognuno dei suoi elementi. Si ottiene cosı il risultato dell’operazione.

53

Page 54: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Figura 3.6: Esempio di Parallel Reduction per il calcolo della somma deglielementi di un vettore.

54

Page 55: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

4Test del software

In questo capitolo si conducono i test sul software sviluppato. Si confronta-

no quindi le function cgmamma, ccgmamma e nvcgmamma, al fine di valutare le

caratteristiche delle tre diverse implementazioni.

4.1 Precisione dei risultati

Si inizia confrontando la precisione delle soluzioni trovate dai tre metodi, per

il sistema (3.1). Il formato dei numeri adoperato nei test e 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.

Definizione del problema

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

Page 56: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

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

• Hdiag=mu+[zeros(n,1)Hijpm12] + [Hijpm12 zeros(n,1)] + [zeros(1,m); Hipm12j

]+[Hipm12j;zeros(1,m)].

In questo modo, la matrice pentadiagonale e sdp, e la convergenza del metodo

iterativo e assicurata. Inoltre il suo numero condizionamento e inversamente

proporzionale al valore del parametro mu. I contenuti di mask ed etan vengono

invece assegnati dai comandi

• mask=single(ones(m,n));

• etan=single(zeros(m,n));

mentre la tolleranza e il massimo numero di iterazioni sono rispettivamente fissati

a 1×10−7 e 2000. Infine, si sceglie il termine noto b, in modo tale che la soluzione

del sistema corrisponda al vettore interamente composto da elementi unitari. Per

fare questo, prima di ogni test, si assegna il valore di b con il seguente comando.

• b=matvec(Hdiag, Hipm12j, Hijpm12,ones(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.

4.2 Tempi di esecuzione

Questo test si occupa di confrontare i tempi di esecuzione delle tre function su

sistemi di dimensioni differenti. Le configurazioni di esecuzione per la GPU,

56

Page 57: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Function m× n mu iterazioni ‖Ax− b‖22cgmamma

512× 512 1004 5,44e-5

ccgmamma 4 5,27e-5

nvcgmamma 4 8,55e-5

cgmamma

512× 512 1018 4,81e-6

ccgmamma 18 4,96e-6

nvcgmamma 18 6,83e-6

cgmamma

512× 512 146 6,30e-6

ccgmamma 46 6,29e-6

nvcgmamma 46 7,18e-6

cgmamma

512× 512 0.1115 1,37e-5

ccgmamma 92 1,37e-5

nvcgmamma 92 1,45e-5

cgmamma

512× 512 0.01273 3,13e-5

ccgmamma 274 3,13e-5

nvcgmamma 273 3,19e-5

cgmamma

1024× 1024 1004 2,17e-4

ccgmamma 4 2,11e-4

nvcgmamma 4 3,43e-4

cgmamma

1024× 1024 1011 1,96e-5

ccgmamma 11 1,94e-5

nvcgmamma 11 2,58e-5

cgmamma

1024× 1024 133 2,60e-5

ccgmamma 33 2,59e-5

nvcgmamma 33 2,958e-5

cgmamma

1024× 1024 0.1121 5,71e-5

ccgmamma 122 5,75e-5

nvcgmamma 121 6,05e-5

cgmamma

1024× 1024 0.01292 1,33e-4

ccgmamma 301 1,37e-4

nvcgmamma 292 1,37e-4

Tabella 4.1: Risultati dei test sulla precisione del risultato. Piu basso e il valoredel residuo, maggiore e l’accuratezza della soluzione.

57

Page 58: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

il compilatore, e l’hardware adoperato, sono determinanti per i risultati del-

le misurazioni; percio, di seguito, si riassumono le caratteristiche dell’hardware

utilizzato.

PC1 Il primo personal computer e il sistema portatile sul quale e stato sviluppato

il codice dei metodi. La CPU del sistema e 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, e 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.

PC2 Il secondo personal computer invece e un sistema desktop. La sua CPU e

una Intel Core 2 Duo E6750 che lavora alla frequenza di 2,66 GHz. La me-

moria cache e anche in questo caso di 4 Mbyte, mentre la memoria centrale

ammonta a 2048MByte, e lavora ad una frequenza di 800 MHz. La scheda

video di questo sistema, rispetto alla precedente, e una piu “performan-

te” NVIDIA GeForce 8800 GTS composta da 12 SM e da una memoria da

384MByte. La GPU opera alla frequenza di 512MHz, mentre la memoria

opera alla frequenza di 792 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 e adoperata la piu “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 specifichera quindi di volta in volta la configurazione utilizzata.

4.2.1 Esecuzione in ambiente Matlab/Linux

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 e lo GNU C Compiler (GCC) 4.1.2, sul quale,

in fase di compilazione, sono state abilitate tutte le ottimizzazioni del codice. La

58

Page 59: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

configurazione d’esecuzione per la GPU e 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 e 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 proprieta 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.

4.2.2 Esecuzione in ambiente Windows

Il secondo test mette a confronto i metodi ccgmamma e nvcgmamma sul sistema

operativo Windows XP. Il compilatore utilizzato e quello fornito con l’ambiente di

sviluppo Microsoft Visual C++TM

2005 Express Edition mentre le configurazioni

di esecuzione sono: 128 blocchi per 128 thread su PC1, e 320 blocchi per 256

thread su PC2.

Il codice con il quale si definisce il problema e il seguente:

host int main( int argc, char∗∗ argv ){int m=512; int n=512; int maxit=1000;

long i ,k; float norm=0.0f; float tol=1e−5; float zero=0.0f;

if (argc==4){

59

Page 60: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Function m× n iterazioni PC 1 (s) speed upcgmamma

512× 512 100029,39 1

ccgmamma 6,22 4,72nvgmamma 4,60 6,39cgmamma

512× 512 5000149,41 1

ccgmamma 31,13 4,79nvgmamma 22,84 6,54cgmamma

512× 512 10000294,65 1

ccgmamma 61,84 4,76nvgmamma 46,41 6,39

cgmamma1024× 1024 500

67,96 1ccgmamma 15,91 4,27nvgmamma 10,03 6,77cgmamma

1024× 1024 1000135,80 1

ccgmamma 32,19 4,21nvgmamma 18,02 7,36cgmamma

1024× 1024 5000674,92 1

ccgmamma 158,50 4,25nvgmamma 91,52 7,37

Tabella 4.2: Test di esecuzione all’interno dell’ambiente Matlab

m=atoi(argv[1]); n=atoi(argv[2]); maxit=atoi(argv[3]);

}cgresult res;

float ∗ hdiag=(float∗)malloc(sizeof(float)∗m∗n);

float ∗ hipm12j=(float∗)malloc(sizeof(float)∗m∗n);

float ∗ hijpm12=(float∗)malloc(sizeof(float)∗m∗(n−1));

float ∗ b=(float∗)malloc(sizeof(float)∗m∗n);

float ∗ mask=(float∗)malloc(sizeof(float)∗m∗n);

float ∗ etan=(float∗)malloc(sizeof(float)∗m∗n);

memset(hipm12j,0,sizeof(float)∗m∗n);

for(i=0;i<(m−1)∗n;i++){k=i+(i)/(m−1);

hipm12j[k]=(rand()%1000)/100.0;

}for(i=0;i<m∗(n−1);i++)

hijpm12[i]=(rand()%1000)/100.0;

for(i=0;i<m∗n;i++){hdiag[i]=(rand()%1000); b[i]=(rand()%1000)/100.0;

mask[i]=1.0; etan[i]=(rand()%1000)/100.0;

}res=nvcgmamma(mask,etan,hdiag,hipm12j, hijpm12, b, tol,maxit,zero,m,n);

free(res.x);

return;

}

Al termine di ogni test, sia il metodo ccgmamma, che il metodo nvcgmamma, stam-

60

Page 61: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

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 upccgmamma

512× 512 10007,11 5,73

nvcgmamma 4,64 1,53 0,48 11,93ccgmamma

512× 512 500035,35 28,54

nvcgmamma 23,14 1,53 2,42 11,79

ccgmamma1024× 1024 1000

33,98 27,70nvcgmamma 18,15 1,87 1,48 18,72ccgmamma

1024× 1024 5000168,92 138,11

nvcgmamma 90,60 1,86 7,40 18,66

ccgmamma1536× 1536 1000 –1 –

64,28nvcgmamma 3,33 19,30ccgmamma

1536× 1536 5000 – –320,07

nvcgmamma 16,14 19,83

ccgmamma2048× 2048 1000 – –

115,96nvcgmamma 5,82 19,92ccgmamma

2048× 2048 5000 – –578,07

nvcgmamma 29,07 19,89nvcgmamma 28,61 20,212

Tabella 4.3: Confronto fra i tempi di esecuzione dei metodi sui sistemi PC1 e PC2

4.3 Rapporto prestazioni/prezzo

L’ultimo confronto di questo capitolo riguarda il rapporto fra prestazioni e prezzo.

Il rapporto prestazioni/prezzo e una delle caratteristiche piu importanti nel campo

industriale, e permette di valutare la convenienza dell’investire in un sistema di

calcolo basato su una GPU, piuttosto che su una CPU. Tuttavia questo rapporto

e strettamente legato, sia al software realizzato, sia all’hardware utilizzato, in

questa tesi. Il seguente test va quindi considerato valido solo in questo contesto.

In Tabella 4.4 si mostrano i dati riguardanti l’hardware di PC2. I prezzi indicati

sono ricavati dalla media dei prezzi raccolti dai listini di piu negozi.

1Non e stato possibile eseguire il test a causa della memoria insufficiente della scheda video.2Il risultato e stato ottenuto con una configurazione di 1000 Blocchi da 256 thread ciascuno.

Inoltre la compilazione e stata effettuata con il flag -maxrregcount 10. Questo flag, limitaa 10 il numero di registri utilizzati per ogni thread, permettendo di raggiungere l’occupazionemassima della GPU.

61

Page 62: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

CPU (e)speed up

prezzo× 103 GPU (e)

speed up

prezzo× 103 rapp. GPU

CPU

1501

150× 103 = 6, 67 110

20, 21

110× 103 = 183, 73 27, 55

Tabella 4.4: Confronto del rapporto prestazioni/prezzo fra CPU e GPU

Questi risultati evidenziano un vantaggio economico derivante dall’utilizzo della

GPU, di circa 27 volte, rispetto a quello ricavato da una CPU. Tuttavia questo

vantaggio e solo una stima della reale convenienza economica che si ha nell’utilizzo

di una GPU, poiche non sono stati considerati i costi della memoria; componente

necessaria all’interno di un sistema.

62

Page 63: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

5Conclusioni

Con il lavoro svolto in questa tesi si sono illustrate le caratteristiche principa-

li delle GPU moderne e della tecnologia CUDA, descrivendone in dettaglio sia

l’architettura, sia il modello di programmazione.

Successivamente, sfruttando questa tecnologia, si e realizzata un’implementa-

zione del Metodo dei Gradienti Coniugati per matrici pentadiagonali simmetriche

e definite positive. In questo processo sono state riscritte le operazioni di alge-

bra lineare utilizzate dal Metodo, in modo tale che queste sfruttassero a fondo

l’architettura parallela delle GPU.

Infine si e effettuato un confronto fra tale implementazione e altre due imple-

mentazioni dello stesso Metodo: la prima in linguaggio C e la seconda in lin-

guaggio Matlab. Il confronto ha evidenziato un notevole vantaggio, in termini di

prestazioni, dell’implementazione CUDA. Infatti, sull’hardware utilizzato, questa

si e dimostrata capace di ridurre fino a 20 volte i tempi di elaborazione1.

Lo stesso confronto stima un significativo vantaggio economico, derivante dal-

l’uso della tecnologia CUDA nel software realizzato. Per l’elaboratore su cui si

sono eseguiti i test, questo vantaggio e stato stimato confrontando il rapporto

prestazioni/prezzo della GPU con il rapporto prestazioni/prezzo della CPU. Si

arriva cosı ad ottenere una convenienza economica della tecnologia CUDA di cir-

ca 27 quello della tecnologia convenzionale, ovvero basata sul solo utilizzo della

CPU.

Questi aspetti costituiscono proprio lo spunto piu interessante per un eventuale

1Per i dettagli si consulti il capitolo 4

63

Page 64: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

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 capacita di calcolo di

piu GPU, si potrebbe comunque incrementare la convenienza economica. Ancora

piu 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 piu significativi.

64

Page 65: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Bibliografia

[1] Luigi Brugnano, Cecilia Magherini, e Alessandra Sestini. Calcolo Numerico,

capitolo 1. Master universita & professioni, 2005.

[2] NVIDIA Corporation. Accelerating MATLAB with CUDA using MEX files.

Relazione tecnica, 2007.

[3] NVIDIA Corporation. CUBLAS Sources, 2008. codice sorgente della libreria

CUBLAS.

[4] NVIDIA Corporation. CUDA Programming Guide 2.0, 2008. http://www.

nvidia.com/object/cuda develop.html.

[5] NVIDIA Corporation. CUDA Reference Manual 2.0, 2008. http://www.

nvidia.com/object/cuda develop.html.

[6] G. Cummins, R. Adams, e T. Newell. Scientific computation through a GPU.

Southeastcon, 2008. IEEE, pp. 244–246, April 2008.

[7] Richard Edgar. Advanced CUDA, June 2008. GPU workshop Slides.

[8] N. Fujimoto. Faster matrix-vector multiplication on geforce 8800gtx. Pa-

rallel and Distributed Processing, 2008. IPDPS 2008. IEEE International

Symposium on, pp. 1–8, April 2008.

[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

Page 66: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

[10] Tom R. Halfhill. Parallel processing with CUDA. Relazione tecnica,

Microprocessor, 2008.

[11] Mark Harris. Optimizing Parallel Reduction in CUDA, 2008. NVIDIA

Developer Technology Slides.

[12] Won-Ki Jeong e Ross Whitaker. High performance computing on the GPU:

NVIDIA G80 and CUDA, 2007. SCI Institute, University of Utah Slides.

[13] David B. Kirk. 10 Important Problems in Computer Architecture, 2008.

NVIDIA Chief Scientist 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.

[16] Michael Macedonia. The GPU Enters Computing’s Mainstream. Computer,

36(10):106–108, 2003.

[17] Mathworks. Matlab 7 C and Fortran API Reference. The Mathworks, Inc.,

2007.

[18] Mathworks. Matlab 7 Desktop Tools and Development Environment. The

Mathworks, Inc., 2007.

[19] Mathworks. Matlab 7 External Interfaces. The Mathworks, Inc., 2007.

[20] Bettelli Oscar. Sistemi di calcolo parallelo. ECPlanet, 2008. www.ecplanet.

com.

[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

Page 67: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

[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.

[25] W. A. Wiggers, V. Bakker, A. B. J. Kokkeler, e G. J. M. Smit. Implementing

the conjugate gradient algorithm on multi-core systems In Proceedings of the

International Symposium on System-on-Chip (SoC 2007), Tampere. A cura

di J. Nurmi, J. Takala, e O. Vainio, numero 07ex1846, pp. 11–14, Piscataway,

NJ, November 2007. IEEE.

[26] Wikipedia. Flynn’s taxonomy — wikipedia, the free encyclopedia, 2008.

[Online; accessed 3-September-2008].

[27] Wikipedia. Graphics processing unit — wikipedia, the free encyclopedia,

2008. [Online; accessed 3-September-2008].

[28] Wikipedia. Legge di Amdahl — wikipedia, l’enciclopedia libera, 2008.

[Online; in data 4-settembre-2008].

[29] Wikipedia. Stream processing — wikipedia, the free encyclopedia, 2008.

[Online; accessed 3-September-2008].

[30] Wikipedia. Z-buffer — wikipedia, l’enciclopedia libera, 2008. [Online; in

data 2-ottobre-2008].

67

Page 68: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

68

Page 69: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Elenco delle tabelle

1.1 Tassonomia di Flynn . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

3.1 Informazioni prodotte dal profiler di Matlab sull’esecuzione del metodo

cgmamma. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

3.2 Confronto dei i tempi di esecuzione, fra le implementazioni Matlab, Host

e Device della funzione matvec. Le ultime due colonne mostrano gli speed

up delle function sviluppate, rispetto all’implementazione originale. A

numero di test uguale corrisponde un egual numero di elementi . . . . . 48

3.3 Confronto dell’implementazione matvec di Matlab con le funzioni Host

e Device sfruttanti la nuova struttura dati. A numero di test uguale

corrisponde un egual numero di elementi . . . . . . . . . . . . . . . . . . 50

4.1 Risultati dei test sulla precisione del risultato. Piu basso e il valore del

residuo, maggiore e l’accuratezza della soluzione. . . . . . . . . . . . . . 57

4.2 Test di esecuzione all’interno dell’ambiente Matlab . . . . . . . . . . . . 60

4.3 Confronto fra i tempi di esecuzione dei metodi sui sistemi PC1 e PC2 . 61

4.4 Confronto del rapporto prestazioni/prezzo fra CPU e GPU . . . . . . . 62

69

Page 70: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

70

Page 71: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

Elenco delle figure

0.1 Pipeline di rendering di una scena tridimensionale. . . . . . . . . . . . . 10

0.2 A confronto l’architettura di una CPU multicore, con quella di un GPU

moderna: a ugual colore corrisponde ugual funzionalita. Nella GPU la

maggior parte della superficie del core e dedicata ad unita Arithmetic

and Logical Unit (ALU) . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

1.1 Pipeline di un processore vettoriale a confronto con la pipeline di un pro-

cessore scalare: nel primo le operazioni di fetch, decodifica, attivazione

della memoria, e writeback sono eseguite una sola volta. . . . . . . . . . 17

2.1 Architettura della GPU G80 di NVIDIA . . . . . . . . . . . . . . . . . . 20

2.2 Texture/Processor Cluster nella CPU G80 . . . . . . . . . . . . . . . . . 22

2.3 Stream Multiprocessor . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

2.4 Schema degli accessi alla memoria . . . . . . . . . . . . . . . . . . . . . 24

2.5 Gerarchia dei thread in CUDA. . . . . . . . . . . . . . . . . . . . . . . . 28

2.6 Gerarchia della memoria in CUDA. . . . . . . . . . . . . . . . . . . . . . 31

2.7 Flusso delle operazioni per un’applicazione costruita con CUDA . . . . . 34

3.1 Effetto della legge di Amdhal. . . . . . . . . . . . . . . . . . . . . . . . . 42

3.2 Moltiplicazione con la diagonale principale . . . . . . . . . . . . . . . . . 45

3.3 Moltiplicazione con le sottodiagonali piu esterne . . . . . . . . . . . . . 45

3.4 Moltiplicazione con le prime sottodiagonali . . . . . . . . . . . . . . . . 45

3.5 Nuovo schema di moltiplicazione per la prima sottodiagonale . . . . . . 49

3.6 Esempio di Parallel Reduction per il calcolo della somma degli elementi

di un vettore. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

71

Page 72: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

72

Page 73: Implementazione efficiente del Metodo dei Gradienti Coniugati in ambiente CUDA

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


Recommended