不可能使用同一内核对象并行执行同一内核的两个实例。
要并行执行同一内核的多个实例,需要从同一程序对象创建多个内核对象,并将它们排队到不同的命令队列中。
即使对主机代码进行了并行化,两个CPU线程也不会保留相同的内核对象。那么“ clRetainKernel” API的目的是什么?
答案 0 :(得分:0)
那么“ clRetainKernel” API的目的是什么?
来自来源https://www.khronos.org/registry/OpenCL/specs/opencl-1.2.pdf
第18页:
引用计数:OpenCL对象的寿命由以下方式确定: 其引用计数-对引用的内部计数 物体。在OpenCL中创建对象时,其引用 计数设为1。随后对适当的keepAPI的调用 (例如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参数)同步。复制数据后,其事件会向另一个队列(队列2)发出信号,以便立即进行计算。但是在第一个队列上,同步是隐式的,因此在没有事件的情况下使数据副本入队后立即对计算进行入队是可以的,因为此处使用的队列是有序队列。在queue2完成计算后,它会通过evt2发出readBuffer信号;
这来自单个GPU示例,对于多个GPU,还需要复制数据。
即使将主机代码并行化,两个CPU线程也没有使用
如果使用事件轮询自旋等待循环完成同步,则它将完全占用其线程。如果您有多个具有相同旋转等待循环的命令队列,则需要这两个线程;您也可以在同一循环中一个接一个地轮询两个事件,但这需要您注意事件处理,以防动态更改命令队列数。使用每线程轮询,可以更轻松地管理代码行的可伸缩性。
要并行执行同一内核的多个实例,需要创建多个内核对象
如果要在多个GPU上同时使用内核,或者要在同一GPU上使用不同的缓冲区,则必须有不同的内核对象。因为设置内核参数不是入队操作。它在完成时返回,并且在内核运行时不应执行,并且在完成后不获取事件就无法知道内核运行的确切时间。但是您可以在内核执行之前添加一个锁存器,并在那里进行回调以及时设置参数。这必须很慢,所以拥有多个对象既更快又更简单。