Так, какова цель API "clRetainKernel"?
Из источника https://www.khronos.org/registry/OpenCL/specs/opencl-1.2.pdf
Страница 18:
Счетчик ссылок: Срок службы объекта OpenCL определяется его счетчиком ссылок - внутренним счетчиком количества ссылок на объект. Когда вы создаете объект в OpenCL, его счетчик ссылок устанавливается равным единице. Последующие обращения к соответствующему retainAPI (например, clRetainContext, clRetainCommandQueue) увеличивают счетчик ссылок. Вызовы к соответствующему releaseAPI (например, clReleaseContext, clReleaseCommandQueue) уменьшают счетчик ссылок. После того как счетчик ссылок достигнет нуля, ресурсы объекта освобождаются OpenCL.
Он увеличивает внутренние счетчики соответствующего объекта opencl и может использоваться вне некоторого блока RAII . Я не использовал его, потому что RAII уже было достаточно. Но если бы возникла проблема «совместного использования», это удержание помогло бы использовать его за пределами его охвата. Таким образом, каждый должен делать свою часть сохранения и освобождения, если он делится чем-то вне его области (особенно, если вместо этого используется C api). В привязках C ++, https://github.khronos.org/OpenCL-CLHPP/cl2_8hpp_source.html#l05668 вы можете видеть, что конструктор
explicit Kernel(const cl_kernel& kernel, bool retainObject = false) : ...
становится владельцем вместо увеличения счетчика ссылок. (сохранить = ложь). Затем, после нескольких строк кода,
(с сохранением)
2447 // We must retain things we obtain from the API to avoid releasing
2448 // API-owned objects.
2449 if (devices) {
2450 devices->resize(ids.size());
2451
2452 // Assign to param, constructing with retain behaviour
2453 // to correctly capture each underlying CL object
2454 for (size_type i = 0; i < ids.size(); i++) {
2455 (*devices)[i] = Device(ids[i], true); // true: retain
2456 }
2457 }
(без сохранения)
6457 kernels->resize(value.size());
6458
6459 // Assign to param, constructing with retain behaviour
6460 // to correctly capture each underlying CL object
6461 for (size_type i = 0; i < value.size(); i++) {
6462 // We do not need to retain because this kernel is being created
6463 // by the runtime
6464 (*kernels)[i] = Kernel(value[i], false); // false: no retain
6465 }
6466 }
ясно говорит: «если вы его создаливам не нужно его сохранять ".
Если это вещь, принадлежащая API, она будет выпущена внутри нее, поэтому, если вам нужно ее использовать, сохраните. Если вы что-то создаете, вы просто создаете и отпускаете.
Невозможно использовать один и тот же объект ядра для параллельного выполнения двух экземпляров одного и того же ядра.
Нет, это возможно, если вы используете различное смещение при каждом запуске nd-диапазона.
cl_event evt;
clEnqueueWriteBuffer(queue,buffer,CL_FALSE,0,100,myCharArray.data(),0,NULL,&evt);
size_t global_work_size = 50;
clEnqueueNDRangeKernel(queue,kernel,1,NULL,&global_work_size,NULL,0, NULL, NULL);
size_t global_work_size_2 = 50;
size_t global_offset_2 = 50;
cl_event evt2; clEnqueueNDRangeKernel(queue2,kernel,1,&global_offset_2,&global_work_size_2,NULL,1, &evt, &evt2);
clEnqueueReadBuffer(queue,buffer,CL_FALSE,0,100,myCharArray.data(),1,&evt2,NULL);
clFlush(queue);
clFlush(queue2);
clFinish(queue2);
clFinish(queue);
Убедитесь, что между очередями имеется синхронизация событий, чтобы можно было увидеть "последние биты"данные в ядре, но с другим смещением при выполнении.
Вторая очередь синхронизируется с первой командой копирования данных (параметр evt). После того, как данные скопированы, их событие сигнализирует о другой очереди (queue2), чтобы они могли теперь вычислять. Но в первой очереди синхронизация неявна, так что постановка в очередь вычислений сразу после постановки в очередь копии данных без события в порядке, потому что используемая очередь здесь в порядке очереди. После того, как queue2 завершает вычисления, он сообщает readBuffer (по evt2);
Это из одного примера GPU, для нескольких GPU вам также необходимо скопировать данные.
Даже если код хоста распараллелен, нет смысла использовать два потока ЦП
Если синхронизация выполняется с ожиданием опроса событийцикл, он полностью занимает свою нить. Если у вас есть несколько командных очередей с одинаковым циклом ожидания вращения, тогда эти два потока необходимы;Вы также можете опрашивать два события одно за другим в пределах одного и того же цикла, но для этого необходимо позаботиться об обработке событий в случае динамического изменения количества очередей команд. С опросом для каждого потока легче управлять масштабируемостью строк кода.
Чтобы параллельно выполнить несколько экземпляров одного и того же ядра, необходимо создать несколько объектов ядра
Если ядро будет использоваться одновременно на нескольких графических процессорах или на одном и том же графическом процессоре, но с разными буферами, то должны быть разные объекты ядра. Потому что установка аргументов ядра не является операцией постановки в очередь. Он возвращается, когда это сделано, и это не должно быть сделано во время работы ядра, и вы не можете знать точное время запуска ядра без получения события после его завершения. Но вы можете добавить защелку перед выполнением ядра и сделать обратный вызов, чтобы установить аргументы вовремя. Это должно быть медленно, поэтому несколько объектов быстрее и проще.