Definire lo spazio degli indici: Set_index_space_d2

Nell’esempio che stiamo trattando, il kernel ha il compito di ricevere due immagini: la prima una gpu_image (sorgente) e la seconda, una  gpu_matrix_2d, destinazione delle elaborazioni; La prima cosa da osservare (ma è banale..) è che l’immagine trasformata è un’istanza di tipo diverso dal sorgente; ovviamente questa situazione dipende da ragioni interne al programma che si sta costruendo: la destinazione potrebbe essere un istogramma, una tabella dei colori presenti nel sorgente, o qualunque altra cosa. In questo caso la scelta è stata quella di usare come destinazione una istanza di gpu_matrix_2d per calcolare un’immagine risultante, inserendola in una matrice bidimensionale; le classi gpu_image e gpu_matrix_2d hanno un costruttore che permette di inizializzare le stesse a partire da istanze della classe IMAGE di Colibri (le  img_src e  img_dest nel codice sopra riportato); entrambe le istanze src e des costruite hanno il compito di trasferire le mappe IMAGE fra CPU->GPU->CPU;

Poichè la gpu_image *src è CL_MEM_READ_ONLY, la mappa associata sarà automaticamente trasferita alla GPU; La gpu_matrix_2d *dest  invece, essendo definita come CL_MEM_WRITE_ONLY,  genera  una mappa sulla GPU ma non trasferirà informazioni;

la img_dest passata al costruttore è un’immagine bidimensionale definita dal tipo (RGB,Lab,BGRO..ecc) e dalle dimensioni; la gpu_matrix_2d così inizializzata genera una mappa sulla GPU che ha lo stesso tipo di pixel, destinata ad accogliere il risultato dell’elaborazione; ad esecuzione effettuata, il comando kernel->PopArg(dest) effettua automaticamente il trasferimendo dei dati calcolati dalla GPU alla istanza img_dest incapsulata nella gpu_matrix_2d dest;  risultato finale: l’oggetto img_dest conterrà l’immagine trasformata della sorgente IMAGE src;

Questo non breve preambolo ha lo scopo di introdurre l’uso del concetto NDRange  di OpenCL.

Abbiamo già spiegato nell’articolo Kernel e Work-Item che, nel paradigma OpenCL, un kernel è una funzione in C-OpenCL (entry point di un programma) che è eseguito su una compute-unit in più istanze parallele. Ogni istanza del kernel è definita work-item.  Nel nostro caso (l’esempio che stiamo analizzando) ogni istanza del kernel riceverà le coordinate di un punto dell’immagine destinazione (quelle del pixel x,y) e, sulla base di esse estrarrà le coordinate del pixel nel sorgente che sarà trasformato ed inserito in posizione (x,y) destinazione; il numero totale di work-item usati sarà nel nostro caso pari al numero di  pixel nell’immagine destinazione; ovviamente non tutti i work-item saranno eseguiti in parallelo: a questo scopo OpenCL definisce il concetto di Work-Group, una matrice di work-item eseguiti in parallelo;

La GPU schedulerà intervalli di work-item in gruppi di elementi di elaborazione, i work-group , fino a quando tutti i work-item non sono stati elaborati. I kernel successivi possono essere eseguiti, fino al completamento di tutta l’applicazione.

Set_index_space_2d()

La funzione definisce il range delle variazioni delle due coordinate x,y passate a ciascun work-item (uno per ogni pixel destinazione in questo esempio, ma sono possibili criteri diversi..); quindi l’istruzione:

if (kernel->Set_index_space_2d(ret_area->dx, ret_area->dy))

Ha definito che il numero totale di work-item da eseguire sarà pari ai valori (ret_area->dx, ret_area->dy),  dimensioni in pixel dell’immagine destinazione; Non è visibile nel codice alcuna istruzione riguardante le dimensioni dei work-group, il loro numero e le dimensioni in righe e colonne di work-item eseguiti in parallelo (appunto il work-group..): per non complicare la vita del programmatore, la scelta è fatta automaticamente dall’interno della gpu_kernel, ad esecuzione della funzione  Set_index_space_2d(), sulla base delle caratteristiche rilevate del device sul quale viene eseguito il kernel;

Nota finale

Abbiamo presentato, attraverso un frammento di codice, come vengono eseguiti i kernel nel framework di Colibri. Abbiamo mostrato come l’uso della classe gpu_kernel e delle classi derivate da gpu_mem_object semplifichi notevolmente le procedure d’accesso alle API OpenCL; Con poche righe di codice abbiamo richiamato un programma, compilato lo stesso, passato in forma standardizzata gli argomenti al kernel, eseguito quest’ultimo ed estratte le informazioni elaborate. Se qualcuno dei lettori volesse cimentarsi con l’uso diretto, non mediato dalle classi del namespace OPCL di Colibri, potrà verificare quanti compiti sono invece svolti in modo completamente nascosto al programmatore dalle classi descritte in questo articolo.

Da quanto abbiamo scritto forse non si evidenzia abbastanza l’importanza delle classi derivate da gpu_mem_object; ci limitiamo per ora a ricordare che le classi standardizzano il modo in cui i diversi tipi di informazione sono passati alla gpu; Ad esempio, la classe gpu_matrix_2d per essere passata correttamente al kernel, deve inserire nello stack degli argomenti la mappa dei punti, le dimensioni dx,dy della stessa, le dimensioni in byte dei punti.. la funzione PushKernel() della classe garantisce in un unica istruzione il passaggio di tutte queste informazioni al kernel nell’ordine atteso:

gli argomenti trasferiti sono in un blocco type *ptr, size_t nr, size nc, size_t s  dove 
type *ptr sono i punti della matrice ( l’interpretazione del tipo di puntatore (type *) è definita dalla dichiarazione del kernel: ad esempio float *ptr)
size_t nr numero di righe nella matrice
size_t nc numero di colonne
size_t se dimensioni in byte di ciascun elemento (di solito è inutile, poiché l’interpretazione è data dalla dichiarazione del kernel (ad esempio float *ptr corrisponde a un se=4 byte) ma viene sempre  trasferita per usi particolari;

Vedremo in seguito che questo metodo permette di standardizzare il modo di scrittura degli argomenti in un programma scritto in C-OpenCL, semplificandone la leggibilità, anche in presenza di un numero molto elevato di variabili.

Infine, si fa notare che la classe contiene alcune funzioni private, friend nei confronti delle classi gpu_program, gpu_buffer e GPU, e pertanto inaccessibili da altri contesti; questo è dovuto alla necessità di proteggere la struttura interna della gpu_kernel da usi impropri;  La funzione  Name() rimanda il nome assegnato nel codice alla funzione C-Opencl __kernel; le funzioni Set_id e Get_id hanno l’evidente significato di assegnare e ottenere l’id cl_kernel di OpenCL; non c’è alcuna necessita d’uso degli stessi nel codice, pertanto non usarli mai.

Infine, la funzione gpu_kernel::Program() rimanda l’istanza gpu_programm che contiene il kernel; anche questa funzione è esposta per usi prevalentemente interni alle classi del namespace OPCL, ma può essere interessante utilizzarla per esplorare le classi incapsulate nella gpu_kernel; ad esempio, questa riga di codice:

	gpu_work_group_str *pdata = &kernel->(Program()->Device()->WorkGroup ( )->pdata);

Permette di risalire alla struttura del work-group  usata dal gpu_kernel *kernel;

Nel prossimo articolo mostreremo come scrivere il codice C-OpenCL di un programma compatibile con le classe del namespace OPCL, in particolare con le modalità di definizione degli argomenti di gpu_kernel, mostrando l’uso delle macro che semplificano la sequenziazione degli stessi.

Pages: 1 2 3 4 5

Leave a Reply

Skip to toolbar