Estou escrevendo um programa OpenCL para uso com minha GPU AMD Radeon HD 7800 series. De acordo com o guia de programação OpenCL da AMD , essa geração de GPU possui duas filas de hardware que podem operar de forma assíncrona.
5.5.6 Fila de comandos
Para Ilhas do Sul e posteriores, os dispositivos suportam pelo menos duas filas de computação de hardware. Isso permite que um aplicativo aumente a taxa de transferência de pequenos despachos com duas filas de comandos para envio assíncrono e possivelmente execução. As filas de computação de hardware são selecionadas na seguinte ordem: primeira fila = filas de comandos OCL pares, segunda fila = filas OCL ímpares.
Para fazer isso, criei duas filas de comando OpenCL separadas para alimentar dados na GPU. Aproximadamente, o programa em execução no thread do host se parece com isso:
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);
Com kNumQueues = 1
, esse aplicativo funciona da maneira esperada: coleta todo o trabalho em uma única fila de comandos que é executada até a conclusão com a GPU estando bastante ocupada o tempo todo. Eu posso ver isso olhando a saída do criador de perfil CodeXL:
No entanto, quando eu defino kNumQueues = 2
, espero que a mesma coisa aconteça, mas com o trabalho dividido igualmente em duas filas. De qualquer forma, espero que cada fila tenha as mesmas características individualmente que a fila: que comece a trabalhar sequencialmente até que tudo esteja pronto. No entanto, ao usar duas filas, posso ver que nem todo o trabalho está dividido nas duas filas de hardware:
No início do trabalho da GPU, as filas conseguem executar alguns kernels de forma assíncrona, embora pareça que nenhum deles ocupa totalmente as filas de hardware (a menos que meu entendimento esteja errado). Perto do final do trabalho da GPU, parece que as filas estão adicionando trabalho sequencialmente a apenas uma das filas de hardware, mas há até momentos em que nenhum kernel está em execução. O que da? Tenho algum mal-entendido fundamental de como o tempo de execução deve se comportar?
Eu tenho algumas teorias sobre por que isso está acontecendo:
As
clCreateBuffer
chamadas intercaladas estão forçando a GPU a alocar recursos de dispositivo de um conjunto de memórias compartilhadas de forma síncrona, que interrompe a execução de kernels individuais.A implementação OpenCL subjacente não mapeia filas lógicas para filas físicas e decide apenas onde colocar objetos em tempo de execução.
Como estou usando objetos GL, a GPU precisa sincronizar o acesso à memória alocada especialmente durante as gravações.
Alguma dessas premissas é verdadeira? Alguém sabe o que pode estar causando a espera da GPU no cenário de duas filas? Todo e qualquer insight seria apreciado!