Nézzük először meg a program keretét. A jelen program egy véletlenszerű adatsort generál, aminek utána kiszámítja a hisztogramját.
Az adatok előkészítése a bemeneti kép és a hisztogram tömb előkészítéséből áll.
unsigned char *buffer =
(unsigned char*)big_random_block( SIZE );
unsigned char *dev_buffer;
unsigned int *dev_histo;
cudaMalloc( (void**)&dev_buffer, SIZE );
cudaMemcpy( dev_buffer, buffer, SIZE, cudaMemcpyHostToDevice );
cudaMalloc( (void**)&dev_histo, 256 * sizeof( int ) );
cudaMemset( dev_histo, 0, 256 * sizeof( int ) );
Az adat előkészítés után már jöhet is a kernel indítása. A kernelt megint csak belső szorzat számításához hasonlóan indítjuk őgy, hogy egy szál több adatot is feldolgozhat.
cudaDeviceProp prop;
cudaGetDeviceProperties( &prop, 0 );
int blocks = prop.multiProcessorCount;
histo_kernel<<<blocks*2,256>>>( dev_buffer, SIZE, dev_histo );
A program most annyiban van "megbolondítva", hogy először lekérdezi a multiprocesszorok számát, és kétszer ennyi blokkot indít, blokkonként 256 szállal. Ezzel a futás közben dinamikusan tudjuk a GPU-hoz igazítani az indított szálstruktúrát.
A program keret többi része csak utómunkákat ír le térjünk inkább rá, hogy mit is csinál a kernel.
__global__ void histo_kernel( unsigned char *buffer,
long size,
unsigned int *histo ) {
// calculate the starting index and the offset to the next
// block that each thread will be processing
int i = threadIdx.x + blockIdx.x * blockDim.x;
int stride = blockDim.x * gridDim.x;
while (i < size) {
atomicAdd( &histo[buffer[i]], 1 );
i += stride;
}
}
A kernel szintén nem túl bonyolult. A belsőszorzathoz hasonlóan a szálak együttesen végigolvassák az adatsort, és minden adatelemnél (vagy pixelnél) nivelik a hisztogram megfelelő számlálóját egyet. A növelés az atomicAdd(...)-al történik, így a végső szám garantáltan helyes lesz.
Ezen a ponton meg kell viszont jegyeznünk, hogy az atomikus műveletek lassítják a programunkat. A GPU nagy teljesítmény a számítások párhuzamos végzésében áll. Egy memória elérés kb. 600 órajelet vesz igénybe (egy írás-olvasásá pár pedig 1200-at), így ha a szálak atomikus műveletek miatt egymásra várnak, az nagyban rontja a hatákonyságot. Ezért atomikus műveleteket csak akkor használjunk, ha tényleg elkerülhetetlen.