Я пишу програму OpenCL для використання з моїм графічним процесором AMD Radeon HD 7800 серії. Відповідно до посібника з програмування OpenCL AMD , це покоління GPU має дві апаратні черги, які можуть працювати асинхронно.
5.5.6 Черга команд
Для Південних островів і пізніших пристроїв підтримуються щонайменше дві апаратні черги обчислень. Це дозволяє додатку збільшити пропускну здатність невеликих відправлень з двома чергами команд для асинхронного подання та, можливо, виконання. Черги для обчислення апаратних засобів вибираються в такому порядку: перша черга = парні черги команд OCL, друга черга = непарні черги OCL.
Для цього я створив дві окремі черги команд OpenCL для подачі даних в GPU. Приблизно програма, що працює на потоці хоста, виглядає приблизно так:
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
, я очікую, що трапиться те саме, але робота рівномірно розділиться на дві черги. Якщо що-небудь, я очікую, що кожна черга матиме ті ж характеристики окремо, як і одна черга: що вона почне працювати послідовно, поки все не буде зроблено. Однак, використовуючи дві черги, я бачу, що не вся робота розбита на дві апаратні черги:
На початку роботи GPU черги примудряються запускати деякі ядра асинхронно, хоча, здається, ніколи повністю не займають апаратні черги (якщо моє розуміння не помиляється). Під кінець роботи графічного процесора здається, що черги послідовно додають роботу лише до однієї з апаратних черг, але є навіть випадки, коли ядра не виконуються. Що дає? Чи є у мене якісь принципові нерозуміння того, як повинен вестись час виконання?
У мене є кілька теорій, чому це відбувається:
Перемежовані
clCreateBuffer
виклики змушують GPU виділяти ресурси пристрою із спільного пулу пам'яті синхронно, який зупиняє виконання окремих ядер.Основна реалізація OpenCL не відображає логічні черги на фізичні черги, а лише вирішує, де розмістити об'єкти під час виконання.
Оскільки я використовую об’єкти GL, GPU потрібно синхронізувати доступ до спеціально виділеної пам'яті під час запису.
Чи правда якесь із цих припущень? Хтось знає, що може спричинити зачеплення GPU у сценарії з двома чергами? Будь-яке розуміння буде вдячне!