“ clRetainKernel”功能的用途是什么?

时间:2019-10-03 06:05:50

标签: opencl

不可能使用同一内核对象并行执行同一内核的两个实例。

要并行执行同一内核的多个实例,需要从同一程序对象创建多个内核对象,并将它们排队到不同的命令队列中。

即使对主机代码进行了并行化,两个CPU线程也不会保留相同的内核对象。那么“ clRetainKernel” API的目的是什么?

1 个答案:

答案 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上使用不同的缓冲区,则必须有不同的内核对象。因为设置内核参数不是入队操作。它在完成时返回,并且在内核运行时不应执行,并且在完成后不获取事件就无法知道内核运行的确切时间。但是您可以在内核执行之前添加一个锁存器,并在那里进行回调以及时设置参数。这必须很慢,所以拥有多个对象既更快又更简单。