2015-12-25 42 views
11

Sto sviluppando un'applicazione OpenCL 1.2 che si occupa di immagini di grandi dimensioni. Al momento, l'immagine che sto testando è 16507x21244 pixel. Il mio kernel viene eseguito in un ciclo che opera su blocchi dell'immagine. Il kernel prende i pezzi 32bpp (rgba) dell'immagine e passa i pezzi di float4 pixel.Come determinare la dimensione massima delle immagini che posso tranquillamente passare al/dal kernel OpenCL?

Definiamo un lato di un blocco (quadrato) in pixel come dimensione del blocco. Vale a dire, un quadrato di pixel di 8192x8192 ha una dimensione del blocco 8192. Ovviamente, sul lato destro e sul lato inferiore abbiamo blocchi rettangolari più piccoli se l'immagine non è chiaramente divisibile per la dimensione del blocco. Il mio codice gestisce questo, ma per il resto di questo post, ignoriamolo per semplicità.

Sono cercando di determinare la dimensione massima del blocco su cui posso operare in ogni iterazione del mio ciclo, nonché la dimensione del blocco ottimale (che potrebbe non essere la dimensione massima del blocco).

Per riferimento, here is the information reported by the clinfo utility on my machine. Sto usando il mio kernel su Geforce GTX 560 Ti con la piattaforma Nvidia usando i loro driver linux proprietari.

La mia ipotesi iniziale ingenua era che potevo operare sulla dimensione massima dell'immagine 2d. Tuttavia, ciò restituisce clEnqueueNDRangeKernel restituendo un codice di errore di -4 (CL_MEM_OBJECT_ALLOCATION_FAILURE).

Pensandoci, questo ha senso per me. Con 1 GB di memoria video, ci si aspetterebbe di poter contenere una singola trama di 16384x16384 pixel (32 bpp) o una trama di 8192x8192 pixel (float4). Se entrambi devono essere memorizzate nella cache sulla carta mentre il kernel funziona, potremmo aspettarci di utilizzare la seguente quantità di memoria:

4 bytes-per-pixel * chunk size^2 (input image) 
+ 16 bytes-per-pixel * chunk size^2 (output image) 
= 1 GiB total video memory 

Risolvendo per dimensione del blocco otteniamo

chunk size = sqrt(1GiB/20) 

Collegando la quantità di memoria riportato da OpenCL (che è un po 'meno di 1 GiB - 1023 MiB) e pavimentazione il risultato, otteniamo:

floor(sqrt(1072889856/20)) = 7324 

Tuttavia, una dimensione pezzo di 7324 risultati ancora in CL_MEM_OBJECT_ALLOCATION_FAILURE.

La mia prossima ipotesi è stata che non possiamo passare un'immagine più grande della dimensione di allocazione massima , che OpenCL riporta come 268222464 byte per la mia scheda. Poiché la mia immagine di output ha una larghezza di pixel maggiore, determinerebbe la mia dimensione del blocco.

floor(sqrt(268222464/16)) = 4094 

Ehi, funziona davvero! Ora, se provassimo ad aumentare? Con mia sorpresa, non fallisce. Attraverso tentativi ed errori, ho ristretto il 6784 come dimensione massima effettiva del blocco. A 6785, inizia a lamentarsi con CL_MEM_OBJECT_ALLOCATION_FAILURE. Non so perché il max sembra essere 6784, e non so se questo è ripetibile o se il valore oscilla (come altri stati esistenti nella memoria video che influenzano quanto può contenere). Trovo anche che correndo con una dimensione del blocco di 6784 è inferiore di alcuni secondi rispetto alla dimensione basata sull'assegnazione massima. Mi chiedo se questo è dovuto al fatto che OpenCL ha bisogno di eseguire più (costose) allocazioni sotto il cofano? Ho anche notato la "dimensione massima dell'argomento del kernel" che OpenCL è in grado di segnalare ( CL_DEVICE_MAX_PARAMETER_SIZE). Tuttavia, quel valore sembra fasullo.Se potessi passare solo 4096 byte, ciò mi limiterà a 16x16 pixel!

Quindi sono lasciati con due domande fondamentali:

  1. Come faccio a determinare la dimensione massima del blocco assoluto?
  2. Come determinare la dimensione del blocco più veloce? (C'è un metodo diverso da prova ed errore?)

Come una domanda bonus, ci sono buone risorse ho potuto girare a per le future domande di questa natura per quanto riguarda basso livello interazioni OpenCL-hardware ?

Infine, fornirò alcuni frammenti di codice per la peer-review; Sarei estremamente grato per qualsiasi critica costruttiva!

Come sempre, grazie in anticipo per qualsiasi aiuto!

+1

Ho un piccolo suggerimento: aggiungi 'assert (err == CL_SUCCESS);' alla riga 112 di 'rgbtolab.d'. So che il buffer dovrebbe essere uguale a NULL se l'allocazione di memoria fallisce, ma forse questo è un bug nel driver di Nvidia. Per quanto riguarda 'CL_DEVICE_MAX_PARAMETER_SIZE', l'oggetto immagine passato al kernel è come un riferimento alla matrice che contiene l'immagine. Non conosco l'esatto modulo interno, cioè struct o pointer, ma è per questo che puoi superare i 16x16 pixel. – chippies

+1

@chippies Se l'errore può verificarsi in un programma ben funzionante (un errore di memoria esaurita è uno di questi errori), non utilizzare assert, in quanto non saranno inclusi nelle versioni di rilascio. Usa il meccanismo di eccezione ('std.exception.enforce' è un modo conveniente per farlo). –

+0

@chippies, grazie, volevo affermarlo ma apparentemente lo mancava! Per quanto riguarda CL_DEVICE_MAX_PARAMETER_SIZE, non so perché non ci ho pensato, heh; cancellando la sezione pertinente nel post. –

risposta

2

per rispondere alle vostre domande dirette:

1) Per determinare la dimensione del blocco massimo assoluto si può usare per una singola operazione di kernel, si deve sapere che cosa 'dimensione del blocco' è in riferimento a. Ad esempio, ci sono cinque modelli di memoria definiti nella struttura di memoria OpenCL. Uno dei quali è la memoria host, che ignoreremo. Gli altri quattro sono globali, costanti, locali e privati.

Per ottenere informazioni sull'hardware in relazione a ciò che può supportare, suggerisco vivamente di passare ai documenti dell'API di Khronos, registrati nella parte inferiore. C'è una gran quantità di metadati sul tuo dispositivo che puoi raccogliere. Ad esempio, ci sono domande per l'altezza massima e la larghezza massima di un'immagine in 2D e/o 3D che il dispositivo può supportare. Ti suggerisco inoltre di dare un'occhiata a CL_DEVICE_LOCAL_MEM_SIZE e CL_DEVICE_MAX_COMPUTE_UNITS per definire i tuoi gruppi di lavoro. Esiste anche una query CL_DEVICE_MAX_MEM_ALLOC_SIZE consentita.

Per sottolineare la tua preoccupazione per le prestazioni è che la dimensione della memoria che ti viene assegnata per lavorare è la dimensione massima ottimale per un gruppo di lavoro o un elemento (a seconda della query). Quello che potrebbe accadere è una fuoriuscita della memoria nello spazio globale. Ciò richiede più allocazioni di memoria tra diversi lavoratori, causando una riduzione delle prestazioni. Non sicuro al 100% su tale affermazione, ma potrebbe benissimo essere parte del problema quando si supera la dimensione del buffer consigliata.

2) Per determinare la dimensione del blocco più veloce, non è necessario eseguire prove ed errori. Nel libro "OpenCL Programming Guide" pubblicato da Addison-Wesley c'è una sezione sull'uso degli eventi per la profilazione nell'applicazione host. Ci sono serie di funzioni che possono essere profilate.Queste funzioni sono le seguenti:

  • clEnqueue {Leggi | Scrivi | Mappa} Buffer
  • clEnqueue {Leggi | Scrivi} BufferRect
  • clEnqueue {Leggi | Scrivi | Mappa} Immagine
  • clEnqueueUnmapMemObject
  • clEnqueueCopyBuffer
  • clEnqueueCopyBufferRect
  • clEnqueueCopyImage
  • clEnqueueCo pyImageToBuffer
  • clEnqueueCopyBufferToImage
  • clEnqueueNDRangeKernel
  • clEnqueueTask
  • clEnqueueNativeKernel
  • clEnqueueAcquireGLObjects
  • clEnqueueReleaseGLObject

Per abilitare questa profiling, una volta crea una coda, il CL_QUEUE_PROFILING_ENABLE bandiera deve essere impostata. Quindi la funzione clGetEventProfilingInfo (cl_event event, cl_profiling_info param_name, size_t param_value_size, void * param_value, size_t * param_value_size_ret); può essere usato per estrarre i dati di temporizzazione. È quindi possibile avere l'applicazione host che fare con questi dati come ti pare come ad esempio:

  • scrivere in un registro profilazione
  • Scrivi verso il buffer di uscita
  • di bilanciamento del carico

Utilizzando questa informazione di profilazione è possibile determinare la "dimensione del blocco" più veloce tramite il software o analiticamente e quindi utilizzare una costante per quella dimensione del blocco su tutta la linea.

Domanda bonus Alcune buone risorse sarà il "OpenCL Programming Guide", pubblicato da Addison Wesley, scritto da Aaftab Munshi, Benedetto R. Gaster, Timothy G. Mattson, James Fung, e Dan Ginsburg. Vorrei anche dire che lo Khronos docs ha molte informazioni.

Come nota a margine Si esegue questo kernel all'interno di un ciclo doppiamente annidati nel codice ospite ... questo tipo di interruzioni l'intera ragione per usare la programmazione parallela. Soprattutto su un'immagine. Vorrei suggerire il refactoring del codice e la ricerca di modelli di programmazione parallela per le operazioni GPU. Fai anche qualche ricerca sulla creazione e l'uso di Memory Barriers in OpenCL. Intel e Nvidia hanno alcuni ottimi documenti ed esempi su questo. Finally, the API docs are always available

+0

Sono confuso su come il ciclo 2d dovrebbe essere refactored (in riferimento alla nota a margine). Se l'immagine si adatta alla memoria, verrà ripetuta solo una volta. Il codice fa tutto il possibile in parallelo in un momento tramite ciò a cui mi riferisco come blocchi. L'unica altra opzione per quanto riguarda il problema sarebbe quella di ottenere una GPU con più memoria (non scalabile), o di distribuire il lavoro su più dispositivi OpenCL, che su questa macchina degraderebbero le prestazioni perché il processore è un single-core (per non parlare del fatto che ciò complicherebbe significativamente il codice). Correggimi se sbaglio! –

+0

Hai ragione, se l'immagine dovesse essere inserita nella memoria non è necessario preoccuparsi di questa mappatura e rimozione della memoria dall'host al dispositivo. Per fortuna, non hai bisogno di questi cicli nidificati e ti preoccupi della gestione delle dimensioni/della memoria. Dopo aver saputo quanto il dispositivo è in grado di gestire le dimensioni della memoria, è possibile calcolare le allocazioni necessarie quando si utilizzano le funzioni per copiare nuovamente il working set della memoria del dispositivo nella memoria host e viceversa utilizzando diverse funzioni host: clEnqueue {Read | Write | Mappa} Buffer clEnqueue {Read | Write} BufferRect clEnqueue {Leggi | Scrivi | Mappa} Immagine clEnqueueUnmapMemObject –