Implémentation d'une section critique dans CUDA
Je suis en train de mettre en œuvre un section critique dans CUDA à l'aide atomique instructions, mais j'ai rencontré quelques difficultés. J'ai créé le programme de test pour montrer le problème:
#include <cuda_runtime.h>
#include <cutil_inline.h>
#include <stdio.h>
__global__ void k_testLocking(unsigned int* locks, int n) {
int id = threadIdx.x % n;
while (atomicExch(&(locks[id]), 1u) != 0u) {} //lock
//critical section would go here
atomicExch(&(locks[id]),0u); //unlock
}
int main(int argc, char** argv) {
//initialize the locks array on the GPU to (0...0)
unsigned int* locks;
unsigned int zeros[10]; for (int i = 0; i < 10; i++) {zeros[i] = 0u;}
cutilSafeCall(cudaMalloc((void**)&locks, sizeof(unsigned int)*10));
cutilSafeCall(cudaMemcpy(locks, zeros, sizeof(unsigned int)*10, cudaMemcpyHostToDevice));
//Run the kernel:
k_testLocking<<<dim3(1), dim3(256)>>>(locks, 10);
//Check the error messages:
cudaError_t error = cudaGetLastError();
cutilSafeCall(cudaFree(locks));
if (cudaSuccess != error) {
printf("error 1: CUDA ERROR (%d) {%s}\n", error, cudaGetErrorString(error));
exit(-1);
}
return 0;
}
Ce code, malheureusement, dur de se fige ma machine pendant plusieurs secondes et enfin les sorties, l'impression du message:
fcudaSafeCall() Runtime API error in file <XXX.cu>, line XXX : the launch timed out and was terminated.
ce qui signifie que l'une de ces boucles while n'est pas de retour, mais il semble que cela devrait fonctionner.
Comme un rappel atomicExch(unsigned int* address, unsigned int val)
atomiquement définit la valeur de l'emplacement de la mémoire stockée dans val
et renvoie le old
valeur. Donc, l'idée derrière mon mécanisme de verrouillage est qu'il est d'abord 0u
si un thread doit obtenir au-delà de la while
boucle et tous les autres threads doivent attendre sur le while
boucle, car ils vont lire locks[id]
comme 1u
. Puis, quand le thread est fait avec le section critiqueil réinitialise la serrure à 0u
alors qu'un autre thread puisse entrer.
Ce qui me manque?
Par ailleurs, je suis de la compilation avec:
nvcc -arch sm_11 -Ipath/to/cuda/C/common/inc XXX.cu
source d'informationauteur John | 2010-01-07
Vous devez vous connecter pour publier un commentaire.
Ok, j'ai compris, et c'est encore un autre-un-de-la-cuda-paradigme-douleurs.
Comme tout bon cuda programmeur sait (avis que je ne me souviens pas de ce qui fait de moi une mauvaise cuda programmeur, je crois), tous les threads d'un warp doivent exécuter le même code. Le code que j'ai écrit fonctionne parfaitement si ce n'est pour ce fait. Comme il est, toutefois, il est fort probable que les deux fils dans la même courbure accès à la même serrure. Si l'un d'entre eux acquiert le verrou, il a juste oublie de l'exécution de la boucle, mais il ne peut pas continuer au-delà de la boucle jusqu'à ce que tous les autres threads dans sa courbure ont terminé la boucle. Malheureusement, l'autre thread ne sera jamais complète, car il est en attente pour le premier à débloquer.
Ici est un noyau qui va faire le tour sans erreur:
par la façon dont u ne faut pas oublier que la mémoire globale, écrit et ! les lectures ne sont pas achevés où u les écrire dans le code ... donc pour que ce soit pratique, vous avez besoin d'ajouter un mondial memfence ie __threadfence()
L'affiche a déjà trouvé une réponse à sa propre question. Néanmoins, dans le code ci-dessous, je suis en fournissant un cadre général pour mettre en œuvre un section critique en CUDA. Plus en détail, le code exécute un bloc de comptage, mais il est facilement modifiable pour accueillir d'autres opérations qui doivent être effectuées dans un section critique. Ci-dessous, je suis également état d'une explication du code, avec certains, "typique" des erreurs dans la mise en œuvre de sections critiques dans CUDA.
LE CODE
EXPLICATION DU CODE
Les sections critiques sont des séquences d'opérations qui doivent être exécutées de manière séquentielle par le CUDA de threads.
Suppose de construire un noyau qui a la tâche de calcul du nombre de thread se bloque d'un fil de grille. Une idée possible est de laisser chaque fil dans chaque bloc ayant
threadIdx.x == 0
augmenter un compteur global. Pour éviter des conditions de course, toutes les augmentations se produisent de manière séquentielle, de sorte qu'ils doivent être intégrés dans une section critique.Le code ci-dessus a deux fonctions du noyau:
blockCountingKernelNoLock
etblockCountingKernelLock
. L'ancien ne pas utiliser une section critique pour augmenter le compteur et, comme on peut le voir, les retours de mauvais résultats. Ce dernier incarne le compteur augmenter à l'intérieur d'une section critique et produit des résultats corrects. Mais comment fonctionne la section critique du travail?La section critique est régi par un état global
d_state
. D'abord, l'état est0
. En outre, deux__device__
méthodes,lock
etunlock
peut modifier cet état. Lelock
etunlock
méthodes peuvent être invoquées que par un seul thread à l'intérieur de chaque bloc et, en particulier, par le thread ayant thread local de l'indice dethreadIdx.x == 0
.Au hasard lors de l'exécution, l'un des fils ayant thread local de l'indice de
threadIdx.x == 0
et global index de threads, disons,t
sera la première invocation de lalock
méthode. En particulier, il lanceraatomicCAS(d_state, 0, 1)
. Car d'abordd_state == 0
puisd_state
sera mis à jour pour1
atomicCAS
sera de retour0
et le fil de sortie dulock
fonction, en passant à la mise à jour de l'instruction. En attendant un tel thread effectue les operations mentionnees, tous les autres threads de tous les autres blocs ayantthreadIdx.x == 0
va exécuter lalock
méthode. Ils devront cependant trouver une valeur ded_state
égal à1
de sorte queatomicCAS(d_state, 0, 1)
effectuera pas de mise à jour et sera de retour1
afin de laisser ces threads d'exécution de la boucle while. Après ce threadt
effectue la mise à jour, puis il exécute launlock
fonction, à savoiratomicExch(d_state, 0)
rétablissant ainsi l'd_state
à0
. À ce stade, au hasard, un autre des threads avecthreadIdx.x == 0
se verrouille de nouveau à l'état.Le code ci-dessus contient également une troisième fonction noyau, à savoir
blockCountingKernelDeadlock
. Cependant, c'est une autre mauvaise exécution de la section critique, conduisant à des blocages. En effet, nous rappelons que les croisements de fonctionner au même rythme et qu'ils se synchronisent après chaque instruction. Donc, quand nous exécutonsblockCountingKernelDeadlock
il y a la possibilité que l'un des threads d'un warp, dire un fil avec fil local de l'indice det≠0
verrouiller l'état. Dans ce cas, les autres threads du même courbure det
notamment avecthreadIdx.x == 0
exécute les mêmes, alors que le rapport de boucle de filt
l'exécution de threads dans la même courbure effectuée au même rythme. En conséquence, tous les threads en attente pour quelqu'un pour déverrouiller l'état, mais aucun autre thread ne sera en mesure de le faire, et le code sera coincé dans une impasse.