Как эффективно поменять местами буферы памяти OpenCL?

Как следует из названия, я ищу, как эффективно поменять местами два буфера OpenCL. Мое ядро ​​использует два буфера gloabl, один в качестве ввода и один в качестве вывода. Однако я вызываю свое ядро ​​в цикле for с одним и тем же NDRange, каждый раз устанавливая аргументы ядра, ставя ядро ​​в очередь и меняя буферы местами, поскольку предыдущий выходной буфер будет начальным значением входного буфера для следующей итерации.

Как здесь правильно поменять местами эти два буфера? Я предполагаю, что копирование буфера обратно на хост в один из уже распределенных массивов и копирование его в следующий входной буфер с использованием clEnqueueWriteBuffer() и clEnqueueReadBuffer() является неэффективным способом. В противном случае я просто использую временную переменную cl_mem для обмена.


person voxeloctree    schedule 14.06.2012    source источник


Ответы (2)


Вам не нужно, просто установите правильные аргументы ядра, используя clSetKernelArg, прежде чем ставить ядро ​​в очередь во второй раз (используя clEnqueueNDRangeKernel). Буферы останутся на устройстве, ничего не будет скопировано обратно на хост.

В этом случае, конечно, ваш буфер должен быть создан с помощью CL_MEM_READ_WRITE.

person Simon    schedule 14.06.2012
comment
Я думал об этом. Но, похоже, мне придется чередовать входные и выходные аргументы ядра на каждой итерации, нет? - person voxeloctree; 15.06.2012
comment
Да, вы будете. Меняйте местами и устанавливайте их на каждой итерации. Это пинг-понг (mathematik.uni-dortmund.de /~goeddeke/gpgpu/) - person Ani; 15.06.2012
comment
Хорошо, я, конечно, думал об этом. Но извините, если я не ясно дал понять в своем вопросе. Второй раз подразумевает два раза. Не миллионы. Иначе какой смысл в массовом параллелизме. Спасибо вам обоим. - person voxeloctree; 15.06.2012
comment
Вы можете использовать std::swap(bufferA, bufferB); перед вызовом setArg, чтобы не использовать отдельный код для четных и нечетных итераций. - person Madcowswe; 23.02.2014

Как и в предыдущем ответе: Нет, вам вообще не нужно менять местами буферы.

Однако я не согласен с предложенным ответом. Функция clSetKernelArg() не является потокобезопасной и не предназначена для вызова в рабочем цикле.

Правильное решение — создать 2 ядра, созданных с использованием одной и той же программы и исходного кода. Этот подход больше соответствует философии программирования OpenCL «Одно ядро ​​для одной задачи». Лучше всего иметь много ядер с одинаковым кодом, но разными аргументами.

Первое ядро ​​будет иметь:

kernel1 = clCreateKernel(program, "mykernel", NULL);
clSetKernelArg(kernel1, 0, &buff1);
clSetKernelArg(kernel1, 1, &buff2);

А другой будет:

kernel2 = clCreateKernel(program, "mykernel", NULL);
clSetKernelArg(kernel2, 0, &buff2);
clSetKernelArg(kernel2, 1, &buff1);

Таким образом, вам не нужно останавливать выполнение на каждой итерации. Вы можете просто запустить:

for(int it=0; it<iter; it++){
    clEnqueueNDRangeKernel(it%2 ? kernel1 : kernel2, ....);
}
clFinish(command);

Этот подход, безусловно, будет лучше, чем изменение аргументов ядра, более эффективным, с меньшим количеством вызовов API. Кроме того, в некоторых системах clSetKernelArgs() может быть блокирующим вызовом из-за плохой реализации API. Так что лучше их по возможности избегать.

person DarkZeros    schedule 24.02.2014
comment
хотя я согласен с тем, что clSetKernelArg не является потокобезопасным, ваше утверждение о том, что вы не сможете запустить следующую итерацию, не завершив предыдущую, неверно, как только вы вызываете clEnqueueNDRangeKernel, все параметры копируются, поэтому вы можете сразу установить все, что хотите, правильно после постановки ядра в очередь. - person alariq; 02.10.2014
comment
Я обновил свой ответ. Вы правы, 99% доступных платформ не блокирует, блокируют только какие-то старые драйвера, или плохо реализованные на clSetKernelArgs() - person DarkZeros; 03.10.2014