• Non ci sono risultati.

Meccanismi hardware per il controllo sul flusso d’esecuzione

flusso d’esecuzione

Prima di addentrarci nei concetti propri del GPGPU si ritiene necessario presentare le tecniche hardware che hanno governato il flusso di esecuzione nelle ultime generazioni di schede grafiche e il costo imposto dalla presenza di cicli e codice condizionale al pieno sfruttamento della performance offerta dalle moderne GPU.

I tre principali meccanismi di controllo del flusso saranno elencati di seguito in ordine cronologico di introduzione:

- condition codes, permette l’esecuzione di istruzioni condizionali di tipo if-then-else e di cicli for e while le cui guardie booleane dipendano da caratteristiche comuni4 ad ogni elemento dello stream. Nel condition

codes non esiste vero e proprio branching; la GPU esegue i comandi presenti su entrambi i lati di un if aggiornandone la memoria, per`o, solo se l’istruzione appartenga effettivamente al ramo definito dalla condi- zione booleana. Ovviamente il condition codes (o predication codes)

4In termini programmativi ci`o implica che il valore booleno debba o essere una costante il cui valore sia noto staticamente oppure debba essere una variabile uniform.

CAPITOLO 2. GPGPU 50 `

e un tipo di controllo sul flusso di esecuzione particolarmente costoso ma per molte GPU di non ultimissima generazione5 `e l’unica forma di

branching supportato.

- Single Instruction Multiple Data branching (SIMD), permette di ese- guire istruzioni condizionali e cicli anche su attributi specifici di un singolo vertice o frammento. In una architettura SIMD tutti i vertex processor o i fragment processor presenti all’interno della scheda grafi- ca devono eseguire nello stesso istante la stessa istruzione. Se il valore della guardia booleana `e identico per tutti i processori attivi allora ci si limita a valutare, nel comando condizionale, le sole istruzioni facenti parte del ramo corrispondente. Nel caso invece che i valori differiscano si attua una politica simile a quella del condition codes. `E ovvio che per non incorrere in un decadimento della performance sarebbe necessario, quando possibile, evitare di incorrere in cicli divergenti. Architetture SIMD sono presenti nei fragment shader delle ultimissime generazioni di hardware grafico come la GeForce 6800 a la ATI X800.

- Multiple Instruction Multiple Data branching (MIMD) , `e l’approccio pi`u simile all’usuale controllo del flusso di esecuzione a cui si `e abituati sulla CPU. Espone lo stesso set di istruzioni del SIMD, permettendo, per`o, ad ogni singolo elemento dello stream di seguire un proprio path computazionale indipendente. Non presenta costi aggiuntivi oltre quelli della valutazione dell’istruzione condizionale. Il MIMD `e una introdu- zione estremamente recente all’interno delle schede grafiche e finora `e implementato unicamente nel vertex shader della GeForce 6800.

5Ancora la GeForceFX 5900 (2003-2004) presentava esclusivamente il condition codes come sola soluzione per governare il flusso di un’applicazione.

CAPITOLO 2. GPGPU 51 In figura 2.2 sono presentati i costi della valutazione, in termini di cicli di clock della scheda grafica, di un set di istruzioni per il branching, il looping e l’invocazione di funzioni presenti nel fragment shader di una GPU attuale.

Istruzione Numero cicli if-then 4 if-then-else 6 call 2 return 2 loop 4

Figura 2.2: Tabella dei costi delle istruzioni condizionali e di salto

2.7

GPGPU: Un esempio concreto

In questo paragrafo si rivisiter`a molto brevemente la pipeline grafica accen- nando a quali siano quelle caratteristiche che acquistano un’ottica diversa in una prospettiva orientata al calcolo general purpose sulla GPU. Per rendere la trattazione meno astratta si presenter`a un piccolo esempio di computazio- ne sulla scheda grafica, in cui verranno nascoste, per il momento, le difficolt`a tecniche in cui si incorre quando si ha a che fare con il calcolo general purpose su un device grafico. Per una esposizione pi`u completa di tale problematiche si rimanda al capitolo quattro.

In modo da aumentare la chiarezza espositiva, ci si riserva di presentare i vari stadi facenti parti della pipeline in un ordine che favorisca la linearit`a logica nell’introduzione dei concetti chiave del GPGPU.

CAPITOLO 2. GPGPU 52 e v2 entrambi di n numeri e di memorizzarne i risultati su di un terzo array, anch’esso di cardinalit`a n.

Figura 2.3: Somma cella a cella di due array tramite GPU

Struttura della memoria: Texture

Nel calcolo general purpose sulla GPU le texture non sono pi`u solo un elemen- to per aumentare la resa visiva di una mesh tridimensionale ma assumono il ruolo ben pi`u importante di strumento primario per la memorizzazione delle informazioni.

Le texture fondamentalmente non sono altro che array bidimensionali6

di colori rgba7; nelle ultimissime versioni di schede grafiche ciascuna compo- nente `e in grado di memorizzare un float a 32 bit, con il risultato che in un

6In realt`a sono anche unidimensionali e tridimensionali ma le schede grafiche sono particolarmente ottimizzate per la gestione di texture 2D.

7Acronimo di red green blue e alpha; `e uno degli standard pi`u noti in computer graphics per la determinazione di un colore tramite la percentuale di saturazione delle corrispettive componenti. Qui sta principalmente ad indicare il fatto che un elemento di texture `e composto da un vettore tetra-dimensionale

CAPITOLO 2. GPGPU 53 singolo elemento di texture sia possibile mantenere 128 bit di informazioni. Si discuteranno in seguito tutti gli aspetti e le problematiche che possono sorgere con la gestione delle texture come elemento di memoria, per ora ci sar`a sufficiente immaginare che in due texture t1 e t2, entrambe di dimen- sioni n x 1, siano stati caricati, dall’applicazione principale scritta in C++, i valori dei due array corrispondenti.

Figura 2.4: Un texel di una texture pu`o contenere quattro floating-point single precision, per un totale 128 bit per texel

Fragment Shader

Il centro computazionale principale di un pacchetto applicativo che si avvalga della GPU come strumento di calcolo `e il fragment shader. Questo almeno per due buoni motivi:

- In una GPU attuale ci sono pi`u fragment processor che vertex processor. Come abbiamo visto in precedenza nella ATI X800 e nella GeForce 6800 il rapporto `e quasi di tre fragment shader per ogni vertex shader. - I valori elaborati da un fragment shader sono immediatamente pronti

per essere memorizzati in una texture mentre, nel caso si usasse un vertex processor, questi dovrebbero passare attraverso il rasterizzatore e il fragment processor, diminuendo il potere di controllo che l’utente ha sulla coerenza dei dati di output.

CAPITOLO 2. GPGPU 54 Un fragment program per eseguire la somma elemento per elemento di due array potrebbe assomigliare a quello riportato in figura 2.5.

uniform sampler2D t1; uniform sampler2D t2; varying vec2 texcoord;

void main() {

gl FragColor=texture2D(t1,texcoord)+texture2D(t2,texcoord); }

Figura 2.5: Fragment program che esegue la somma dei due vettori

Rasterizzatore `

E evidente, dalla struttura del codice precedente, che si abbia la necessit`a di far eseguire il fragment program una volta per ogni singolo elemento dell’ar- ray n-dimensionale. Ci`o implica, in altre parole, il bisogno di definire uno stream composto da n frammenti distinti. Nel calcolo general purpose sulla GPU questo si ottiene disegnando, nell’applicazione principale, un rettangolo di dimensioni n x 1.

Come si `e visto nel primo capitolo il compito fondamentale del rasteriz- zatore `e quello di identificare i singoli frammenti componenti una primitiva grafica; nel caso in esame8 una volta definiti i quattro vertici che delimitano il quadrato lo stadio di rastering provveder`a a generere gli n elementi che an-

8Si ritiene necessario risottolineare come per favorire un approccio per gradi con le insidie insite nel GPGPU si `e preferito nascondere molti ed importantissimi dettagli tecnici ed implementativi.

CAPITOLO 2. GPGPU 55 glBegin(GL_QUADS); glTexCoord2f(0.0,0.0); glVertex2f(0.0,0.0); glTexCoord2f(1.0,0.0); glVertex2f(n,0.0); glTexCoord2f(1.0,1.0); glVertex2f(n,1.0); glTexCoord2f(0.0,1.0); glVertex2f(0.0,1.0); glEnd();

Figura 2.6: Costruzione di un quad per attivazione frammenti e definizione delle coordinate di texture.

dranno a formare lo stream di input del fragment processor. Tali frammenti, concettualmente, andranno a rappresentare le singole celle di un array.

Si noti come nel codice di figura 2.6 siano state definite anche le coor- dinate di texture agli estremi del rettangolo; questo per permettere ad ogni frammento di poter accedere alla porzione di texture contente le informazioni relative al frammento stesso. Nell’esempio precedente tali informazioni so- no rappresentate dai singoli valori dei due array. La corretta interpolazione delle coordinate di texture definite ai vertici del rettangolo, come abbiamo visto nel capitolo precedente, rientra nel novero dei compiti classici devoluti all’unit`a di rastering.

Render to texture

Fine ultimo del processo di rendering `e, come gi`a introdotto, passare da una descrizione tridimensionale di una scena ad un’immagine bidimensionale del-

CAPITOLO 2. GPGPU 56

Figura 2.7: Ogni frammento attivato dal quad fa riferimento ad una cella dell’array

la scena stessa facilmente rappresentabile sullo schermo di un pc. `E normale quindi che se non si adottasse alcun accorgimento il risultato nella nostra computazione, invece di essere memorizzato su di una texture, sarebbe vi- sualizzato sul monitor del computer, sottoforma di un rettangolo formato da pixel di colore diverso. Tali colori non sarebbero casuali ma rappresen- terebbero, ovviamente, il risultato della somma dei due array. `E preferibile, quantomeno per scopi di debugging, che l’output di una computazione sia mantenuto in una texture. Per far questo si usa una modalit`a denominata render to texture, tramite la quale si rende noto alla pipeline grafica di memo- rizzare i pixel componenti l’immagine non pi`u sul frame buffer (e quindi sullo schermo) ma su di una texture usata come target del processo di rendering. Vertex Shader

Come abbiamo visto in precedenza il compito principale del vertex shader, nella sua accezione canonica, `e quello di trasformare i vertici di un oggetto tridimensionale in coordinate 2D di schermo e di provvedere al calcolo del- l’illuminazione. Nel nostro esempio la computazione sull’influsso delle fonti

CAPITOLO 2. GPGPU 57

Figura 2.8: `E sufficiente definire le coordinate di texture ai quattro angoli del quad per poter permettere ad un frammento di accedere alla sua porzione di texture. Il rasterizzatore, interpolando, genera i valori corretti per ogni singolo fragment.

luminose non ha alcun senso. Il nostro vertex program dovr`a semplicemente trasformare quattro vertici del nostro rettangolo(che peraltro sono gi`a bidi- mensionali. . . ) in coordinate di schermo e passare al rasterizzatore (e quindi al fragment program) le coordinate di texture da interpolare.

CAPITOLO 2. GPGPU 58 varying vec2 texcoord;

void main() {

texcoord = gl MultiTexCoord0; gl Position = ftransform(); }

Figura 2.9: Vertex program minimale per la somma di due array Gather e Scatter

`

E importante notare come i frammenti (celle dell’array) a cui si applica la computazione definita dal fragment processor siano quelli del vettore che do- vr`a contenere il risultato del calcolo. Questo perch`e, come vedremo meglio in seguito, mentre la lettura da una texture di un valore non direttamen- te riferibile al frammento in esame `e una operazione relativamente semplice (gather ), la scrittura (scatter ) dello stesso `e praticamente impossibile. GLSL prevede come unica modalit`a per la memorizzazione di un dato l’assegna- mento di tale valore alla variabile gl FragColor. Tale variabile indica, in uno shader “classico”9 il colore che il frammento in esame andr`a ad assumere sul-

lo schermo o sulla texture usata come target del rendering. Nel caso invece di uno shader GPGPU il dato contenuto nella gl FragColor non verr`a pi`u interpretato come un colore ma come il risultato della computazione che ha per soggetto il frammento in esame.

Lo scatter rappresenta uno dei maggiori limiti sulla classe di algoritmi implementabili sulla scheda grafica. Uno dei principali risultati di questo

9Uno shader che non abbia ambizioni di general purpose computation on GPU ma solo di pura visualizzazione di una scena.

CAPITOLO 2. GPGPU 59 lavoro sar`a mostrare un metodo per aggirare il problema dello scattering nelle mesh tetraedrali a connettivit`a variabile dinamicamente, trasformando lo scatter stesso in una serie di pi`u semplici operazioni di gathering.

Documenti correlati