J'écris un programme OpenCL à utiliser avec mon GPU AMD Radeon HD 7800. Selon le guide de programmation OpenCL d'AMD , cette génération de GPU dispose de deux files d'attente matérielles pouvant fonctionner de manière asynchrone.
5.5.6 File d'attente de commandes
Pour les îles du Sud et les versions ultérieures, les périphériques prennent en charge au moins deux files d'attente de calcul matériel. Cela permet à une application d'augmenter le débit de petits envois avec deux files d'attente de commandes pour la soumission asynchrone et éventuellement l'exécution. Les files d'attente de calcul matériel sont sélectionnées dans l'ordre suivant: première file d'attente = files d'attente de commandes OCL paires, deuxième file d'attente = files d'attente OCL impaires.
Pour ce faire, j'ai créé deux files d'attente de commandes OpenCL distinctes pour alimenter le GPU en données. En gros, le programme exécuté sur le thread hôte ressemble à ceci:
static const int kNumQueues = 2;
cl_command_queue default_queue;
cl_command_queue work_queue[kNumQueues];
static const int N = 256;
cl_mem gl_buffers[N];
cl_event finish_events[N];
clEnqueueAcquireGLObjects(default_queue, gl_buffers, N);
int queue_idx = 0;
for (int i = 0; i < N; ++i) {
cl_command_queue queue = work_queue[queue_idx];
cl_mem src = clCreateBuffer(CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, ...);
// Enqueue a few kernels
cl_mem tmp1 = clCreateBuffer(CL_READ_WRITE);
clEnqueueNDRangeKernel(kernel1, queue, src, tmp1);
clEnqueueNDRangeKernel(kernel2, queue, tmp1, tmp1);
cl_mem tmp2 = clCreateBuffer(CL_READ_WRITE);
clEnqueueNDRangeKernel(kernel2, queue, tmp1, tmp2);
clEnqueueNDRangeKernel(kernel3, queue, tmp2, gl_buffer[i], finish_events + i);
queue_idx = (queue_idx + 1) % kNumQueues;
}
clEnqueueReleaseGLObjects(default_queue, gl_buffers, N);
clWaitForEvents(N, finish_events);
Avec kNumQueues = 1
, cette application fonctionne à peu près comme prévu: elle collecte tout le travail dans une file d'attente de commandes unique qui s'exécute ensuite avec le GPU étant assez occupé tout le temps. Je peux voir cela en regardant la sortie du profileur CodeXL:
Cependant, lorsque je me mets en place kNumQueues = 2
, je m'attends à ce que la même chose se produise, mais avec un travail réparti également sur deux files d'attente. Si quoi que ce soit, je m'attends à ce que chaque file d'attente ait les mêmes caractéristiques individuellement que la file d'attente: qu'elle commence à fonctionner séquentiellement jusqu'à ce que tout soit fait. Cependant, lorsque j'utilise deux files d'attente, je peux voir que tout le travail n'est pas réparti entre les deux files d'attente matérielles:
Au début du travail du GPU, les files d'attente parviennent à exécuter certains noyaux de manière asynchrone, bien qu'il semble que ni n'occupe jamais complètement les files d'attente matérielles (sauf si ma compréhension est erronée). Vers la fin du travail GPU, il semble que les files d'attente ajoutent du travail séquentiellement à une seule des files d'attente matérielles, mais il arrive même qu'aucun noyau ne soit en cours d'exécution. Ce qui donne? Ai-je une incompréhension fondamentale de la façon dont le runtime est censé se comporter?
J'ai quelques théories sur la raison pour laquelle cela se produit:
Les
clCreateBuffer
appels entrecoupés forcent le GPU à allouer des ressources de périphérique à partir d'un pool de mémoire partagée de manière synchrone, ce qui bloque l'exécution de noyaux individuels.L'implémentation OpenCL sous-jacente ne mappe pas les files d'attente logiques aux files d'attente physiques et décide uniquement où placer les objets lors de l'exécution.
Étant donné que j'utilise des objets GL, le GPU doit synchroniser l'accès à la mémoire spécialement allouée lors des écritures.
Certaines de ces hypothèses sont-elles vraies? Quelqu'un sait-il ce qui pourrait faire attendre le GPU dans le scénario à deux files d'attente? Toute idée serait appréciée!