Comment les threads sont-ils organisés pour être exécutés par un GPU?
Si, par exemple, un périphérique GPU dispose de 4 unités de multitraitement et qu'il peut exécuter 768 threads chacun: alors à un moment donné, pas plus de 4 * 768 threads fonctionneront réellement en parallèle (si vous avez planifié plus de threads, ils seront en attente). leur tour).
les discussions sont organisées en blocs. Un bloc est exécuté par une unité de multitraitement. Les threads d'un bloc peuvent être identifiés (indexés) à l'aide d'indices 1Dimension (x), 2Dimensions (x, y) ou 3Dim (x, y, z), mais dans tous les cas x y z <= 768 pour notre exemple (d'autres restrictions s'appliquent à x, y, z, voir le guide et les capacités de votre appareil).
Évidemment, si vous avez besoin de plus de 4 * 768 fils, vous avez besoin de plus de 4 blocs. Les blocs peuvent également être indexés 1D, 2D ou 3D. Une file de blocs attend pour entrer dans le GPU (car, dans notre exemple, le GPU dispose de 4 multiprocesseurs et seuls 4 blocs sont exécutés simultanément).
Supposons que nous voulions qu'un thread traite un pixel (i, j).
Nous pouvons utiliser des blocs de 64 threads chacun. Alors nous avons besoin de 512 * 512/64 = 4096 blocs (donc avoir 512x512 threads = 4096 * 64)
Il est courant d'organiser (pour faciliter l'indexation de l'image) les threads dans des blocs 2D ayant blockDim = 8 x 8 (les 64 threads par bloc). Je préfère l'appeler threadsPerBlock.
dim3 threadsPerBlock(8, 8); // 64 threads
et gridDim 2D = 64 x 64 blocs (les 4096 blocs nécessaires). Je préfère l'appeler numBlocks.
dim3 numBlocks(imageWidth/threadsPerBlock.x, /* for instance 512/8 = 64*/
imageHeight/threadsPerBlock.y);
Le noyau est lancé comme ceci:
myKernel <<<numBlocks,threadsPerBlock>>>( /* params for the kernel function */ );
Enfin: il y aura quelque chose comme "une file d'attente de 4096 blocs", où un bloc attend l'un des multiprocesseurs du GPU pour exécuter ses 64 threads.
Dans le noyau, le pixel (i, j) à traiter par un fil est calculé comme suit:
uint i = (blockIdx.x * blockDim.x) + threadIdx.x;
uint j = (blockIdx.y * blockDim.y) + threadIdx.y;
supposons un GPU 9800GT: 14 multiprocesseurs, chacun ayant 8 processeurs de threads et warpsize 32, ce qui signifie que chaque processeur de thread gère jusqu'à 32 threads. 14 * 8 * 32 = 3584 est le nombre maximal de filetages simultanés.
si vous exécutez ce noyau avec plus de 3584 threads (disons 4000 threads et peu importe comment vous définissez le bloc et la grille. gpu les traitera de la même façon):
func1();
__syncthreads();
func2();
__syncthreads();
alors l'ordre d'exécution de ces deux fonctions est le suivant:
1.func1 est exécuté pour les premiers 3584 threads
2.func2 est exécuté pour les premiers 3584 threads
3.func1 est exécuté pour les threads restants
4.func2 est exécuté pour les threads restants