2016-05-30 43 views
6

A partire da ora, la mia GPU è più lenta della mia CPU quando si tratta del tempo di esecuzione del kernel. Ho pensato che forse dal momento che stavo testando con un piccolo campione, la CPU ha finito per finire più velocemente a causa di un sovraccarico di avvio più piccolo. Tuttavia, quando ho testato il kernel con dati quasi 10 volte la dimensione del campione, la CPU stava ancora finendo più velocemente e la GPU era quasi 400ms dietro.Ottimizzazione del codice del kernel in opencl per una GPU

Runtime con il file 2.39MB CPU: 43.511ms GPU: 65.219ms

Runtime con file di 32.9MB CPU: 289.541ms GPU: 605.400ms

Ho provato ad utilizzare memoria locale, anche se io Sono sicuro al 100% che stavo usando male e ho avuto due problemi. Il kernel termina ovunque tra 1000-3000 ms (a seconda della dimensione che ho impostato per localWorkSize) o mi imbatto in un codice di stato di -5, che è CL_OUT_OF_RESOURCES.

Ecco il kernel che un altro membro SO mi ha aiutato.

__kernel void lowpass(__global float *Array, __global float *coefficients, __global float *Output) { 

int globalId = get_global_id(0); 
float sum=0.0f; 
for (int i=0; i< 65; i++) 
{ 
    float tmp=0; 
    if (globalId+i > 63) 
    { 
     tmp=Array[i+globalId-64]*coefficients[64-i];  

    } 

    sum += tmp; 

} 
Output[globalId]=sum; 
} 

Questo è stato il mio tentativo di utilizzare la memoria locale. Il primo bit sarà uno snippet dal codice host e la seguente parte è il kernel.

//Set the size of localMem 
status |= clSetKernelArg(
    kernel, 
    2, 
    1024, //I had num_items*(float) but it gave me a -5. Num items is the amount of elements in my array (around 1.2 million elements) 
    null); 
printf("Kernel Arg output status: %i \n", status); 

//set a localWorkSize 
localWorkSize[0] = 64; 

//execute the kernel with localWorkSize included 
status = clEnqueueNDRangeKernel(
    cmdQueue, 
    kernel, 
    1, 
    NULL, 
    globalWorkSize, 
    localWorkSize, 
    0, 
    NULL, 
    &someEvent); 


//Here is what I did to the kernel*************************************** 
__kernel void lowpass(__global float *Array, __global float *coefficients, __global float *Output, __local float *localMem) { 

int globalId = get_global_id(0); 
int localId = get_local_id(0); 

localMem[localId] = globalId[globalId]; 

float sum=0.0f; 
for (int i=0; i< 65; i++) 
{ 
    float tmp=0; 
    if (globalId+i > 63) 
    { 
     tmp=localMem[i+localId-64]*coefficients[64-i]; 

    } 

    sum += tmp; 

} 
Output[globalId]=sum; 
} 

collegamento di riferimento che ho usato quando si cerca di impostare le variabili locali: How do I use local memory in OpenCL?

link utilizzato per trovare kernelWorkGroupSize (questo è il motivo per cui ho 1.024 set nel kernelArg): CL_OUT_OF_RESOURCES for 2 millions floats with 1GB VRAM?

I' Ho visto altre persone hanno problemi simili in cui la GPU è più lenta della CPU, ma per molti di loro usano clEnqueueKernel invece di clEnqueueNDRangeKernel.

Heres la mia domanda precedente, se avete bisogno di più informazioni su questo kernel: Best approach to FIFO implementation in a kernel OpenCL

Trovato alcuni trucchi di ottimizzazione per aswell di GPU. https://developer.amd.com/wordpress/media/2012/10/Optimizations-ImageConvolution1.pdf

Codice modificato; Errore esiste ancora

__kernel void lowpass2(__global float *Array, __global float *coefficients, __global float *Output) { 

int globalId = get_global_id(0); 
float sum=0.0f; 
float tmp=0.0f; 
for (int i=64-globalId; i< 65; i++) 
{ 

tmp = 0.0f; 
tmp=Array[i]*coefficients[i];  
sum += tmp; 

} 
Output[globalId]=sum; 
} 
+1

Sono abbastanza sicuro che * veramente * non si vuole uno 'if()' -statement nel tuo inner 'for'-loop. Un compilatore intelligente * potrebbe * essere in grado di sollevare "if" dal ciclo, ma un driver gpu * probabilmente * non ha il tempo o le capacità per farlo in modo efficiente. – EOF

+0

Quale problema/algoritmo stai risolvendo/implementando? – mfa

+0

@EOF Vado a dare un'occhiata alle istruzioni switch come alternativa a if(). – VedhaR

risposta

5

Eseguire il seguente kernel per 24 milioni array elemento

__kernel void lowpass(__global float *Array, __global float *coefficients, __global float *Output) { 

int globalId = get_global_id(0); 
float sum=0.0f; 
for (int i=0; i< 65; i++) 
{ 
    float tmp=0; 
    if (globalId+i > 63) 
    { 
     tmp=Array[i+globalId-64]*coefficients[64-i];  

    } 

    sum += tmp; 

} 
Output[globalId]=sum; 
} 

è completato meno di 200 ms per un pool di dispositivi unità 25 di elaborazione ma oltre 500 ms per una CPU a 8 core.

O una CPU di fascia alta e una gpu di fascia bassa o il driver gpu è stato gimpato o l'interfaccia pci-e di gpu è bloccata su pci-e 1.1 @ larghezza di banda 4x in modo che le copie di matrice tra host e dispositivo siano limitate .

D'altra parte, questa versione ottimizzata:

__kernel void lowpass(__global __read_only float *Array,__constant float *coefficients, __global __write_only float *Output) { 

     int globalId = get_global_id(0); 
     float sum=0.0f; 
     int min_i= max(64,globalId)-64; 
     int max_i= min_i+65; 
     for (int i=min_i; i< max_i; i++) 
     { 
      sum +=Array[i]*coefficients[globalId-i];  
     } 
     Output[globalId]=sum; 
} 

ha meno di 150 ms per CPU (unità di elaborazione 8) e sotto 80ms per GPU (unità di elaborazione 25) calcolare volte. Il lavoro per articolo è solo 65 volte. Questo basso numero di operazioni potrebbe essere facilmente accelerato usando __constant e __read_only e __write_only specificatori di parametri e alcune riduzioni di lavoro su interi.

L'utilizzo di float4 anziché di tipo float per Array e Output dovrebbe aumentare la velocità di% 80 sia per la CPU che per la GPU poiché quelle sono di tipo SIMD e unità di calcolo vettoriale.

colli di bottiglia di questo kernel sono:

  • Solo 65 moltiplicazioni e 65 sommatorie per thread.
  • Ma i dati viaggiano ancora sull'interfaccia pci-express, lentamente.
  • Anche 1 controllo condizionale (i < max_i) per operazione flottante è elevato, richiede lo srotolamento del ciclo.
  • Tutto ciò che è scalare anche se la tua CPU e GPU sono vettoriali.

generale:

  • kernel in esecuzione per la prima volta innesca appena in tempo ottimizzazione del compilatore di OpenCL, lento. Esegui almeno 5-10 volte per i tempi esatti.
  • __ spazio costante è solo 10 - 100 kB ma è più veloce di __global ed è buono per la serie hd5000 di amd.
  • Il sovraccarico del kernel è di 100 microsecondi mentre 65 operazioni di cache sono inferiori a quello ed è ombreggiato dal tempo di sovraccarico del kernel (e ancora peggio, dalla latenza di pci-e).
  • Troppi pochi elementi di lavoro rendono il rapporto di occupazione inferiore, lento.

anche:

  • 4-core Xeon @ 3 GHz è molto più veloce di 16 (1/4 di vliw5) * 2 (unità di elaborazione) = 32 core di GPU @ 600 MHz causa del ramo previsione, larghezza di banda della cache totale, latenza delle istruzioni e latenza senza pc.
  • Le schede amd serie HD5000 sono legacy, come gimped.
  • HD5450 ha 166 GB/s di banda costante memoria
  • che ha anche solo 83 GB/s LDS (memoria locale) banda
  • che ha anche 83 GB/s L1 e L2 larghezze di banda della cache quindi basta lasciarlo lavorare su __global driver optimization invece di LDS a meno che non pianifichi di aggiornare il tuo computer. (per Array ofcourse) Forse, elementi dispari da LDS, anche elementi da __global potrebbero avere 83 + 83 = 166 GB/s di larghezza di banda. Puoi provare. Forse due a due è meglio che alternare in termini di conflitti bancari.

  • L'utilizzo di coefficienti come __constant (166 GB/s) e Array come __global dovrebbe fornire 166 + 83 = 249 GB/s di larghezza di banda combinata.

  • Ogni elemento coefficiente viene utilizzato per una sola volta per thread in modo non sto suggerendo di utilizzare registri privati ​​(499 GB/s)

+0

Sto usando un Intel Xeon 3580 a 3,33 GHz (abbastanza sicuro che abbia 4 core) e per la scheda grafica è un Radeon 5450. Ho cercato le unità di calcolo e apparentemente la Radeon ha solo 2 unità. È bene sapere che il codice non è esente da errori qui – VedhaR

+0

Codice ottimizzato per avere una velocità 3x ma non è sicuro se ha un'uscita corretta. –

+0

Anche HD5450 è un'architettura vettoriale e il tuo kernel è di tipo scalare, quindi sia cpu che gpu sono sottoutilizzati. Dovresti cambiarlo in versione vettoriale. Lo proverò allo stesso tempo. Ma il tipo di vettore lo rende molto difficile e la nuova tecnologia gpu è scalare oggi. –

3

Prima di introdurre prima dichiarazione mossa if Let memoria locale fuori dal giro:

__kernel void lowpass(__global float *Array, __global float *coefficients, __global float *Output) 
{ 
int globalId = get_global_id(0); 
float sum=0.0f; 
int start = 0; 
if(globalId < 64) 
    start = 64-globalId; 
for (int i=start; i< 65; i++) 
    sum += Array[i+globalId-64] * coefficients[64-i];  
Output[globalId]=sum; 
} 

Poi introduzione della memoria locale potrebbe essere implementato in questo modo:

__kernel void lowpass(__global float *Array, __global float *coefficients, __global float *Output) 
{ 
    int globalId = get_global_id(0); 
    int local_id = get_local_id(0); 

    __local float local_coefficients[65]; 
    __local float local_array[2*65]; 

    local_coefficient[local_id] = coefficients[local_id]; 
    if(local_id == 0) 
     local_coefficient[64] = coefficients[64]; 
    for (int i=0; i< 2*65; i+=get_local_size(0)) 
    { 
     if(i+local_id < 2*65) 
      local_array[i+local_id] = Array[i+global_id]; 
    } 
    barrier(CLK_LOCAL_MEM_FENCE); 

    float sum=0.0f; 
    int start = 0; 
    if(globalId < 64) 
     start = 64-globalId; 
    for (int i=start; i< 65; i++) 
     sum += local_array[i+local_id] * local_coefficient[64-i];  
    Output[globalId]=sum; 
} 

PS Potrebbero esserci alcuni errori come i ricalcoli dell'indice globale o locale, ecc. (Sto per andare a dormire ora :)) Tuttavia, l'implementazione dovrebbe metterti nella giusta direzione su come iniziare a utilizzare la memoria locale.

+0

Grazie per la tua risposta! Posso dire che la rimozione dell'istruzione if ha migliorato il legame del kernel di 150 ms. Ad ogni modo l'aggiunta di memoria locale ha praticamente fatto saltare a 900 ms (il doppio di quello che era). Ma, usando quell'ultima implementazione che hai fornito, penso di poter rendere la musica rave ora haha, ha cambiato la canzone nel modo più strano possibile. – VedhaR

+0

Ma ho l'idea, invece di usare la memoria globale per fare riferimento ai coefficienti, posso portare quei valori in locale e usarlo in quel modo (dovrebbe essere più veloce e i coefficienti non cambiano). Tuttavia, qual è il valore di localId in questo caso? – VedhaR

+0

Nell'esempio "localWorkSize [0] = 64;" e io uso lo stesso. Per copiare da '__global' nel buffer' __local' i 64 elementi di lavoro copiano i primi 64 valori (ogni elemento di lavoro copia un valore come '__local' significa che il buffer è condiviso/visibile a tutti gli oggetti di lavoro), quindi il primo elemento di lavoro copia l'ultimo valore. – doqtor