Я пишу программу OpenCL для использования с моим графическим процессором AMD Radeon HD 7800 серии. Согласно руководству по программированию AMD OpenCL , это поколение GPU имеет две аппаратные очереди, которые могут работать асинхронно.
5.5.6 Очередь команд
Для Южных островов и более поздних версий устройства поддерживают как минимум две очереди аппаратных вычислений. Это позволяет приложению увеличить пропускную способность небольших диспетчеров с двумя очередями команд для асинхронной отправки и, возможно, выполнения. Очереди аппаратных вычислений выбираются в следующем порядке: первая очередь = четные очереди команд OCL, вторая очередь = нечетные очереди OCL.
Для этого я создал две отдельные очереди команд OpenCL для подачи данных в графический процессор. Грубо говоря, программа, запущенная на хост-потоке, выглядит примерно так:
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);
При kNumQueues = 1
этом это приложение работает в основном так, как задумано: оно собирает всю работу в одну очередь команд, которая затем выполняется до конца, и GPU все время довольно занят. Я могу увидеть это, посмотрев на вывод профилировщика CodeXL:
Однако, когда я установлю kNumQueues = 2
, я ожидаю, что произойдет то же самое, но с работой, равномерно распределенной по двум очередям. Во всяком случае, я ожидаю, что каждая очередь будет иметь те же характеристики в отдельности, что и одна очередь: она начинает работать последовательно, пока все не будет сделано. Однако при использовании двух очередей я вижу, что не вся работа разбита на две аппаратные очереди:
В начале работы графического процессора очередям удается запускать некоторые ядра асинхронно, хотя кажется, что ни одна из них не занимает полностью аппаратные очереди (если я не ошибаюсь). Ближе к концу работы с графическим процессором кажется, что очереди добавляют работу последовательно только в одну из аппаратных очередей, но бывают даже случаи, когда ядра не работают. Что дает? Есть ли у меня какое-то фундаментальное недопонимание того, как должна себя вести среда выполнения?
У меня есть несколько теорий относительно того, почему это происходит:
Перемежающиеся
clCreateBuffer
вызовы заставляют графический процессор синхронно распределять ресурсы устройства из общего пула памяти, что останавливает выполнение отдельных ядер.Базовая реализация OpenCL не отображает логические очереди в физические очереди, а только решает, куда поместить объекты во время выполнения.
Поскольку я использую объекты GL, графический процессор должен синхронизировать доступ к специально выделенной памяти во время записи.
Являются ли какие-либо из этих предположений верными? Кто-нибудь знает, что может заставить GPU ждать в сценарии с двумя очередями? Любое понимание будет оценено!