虽然从一个内核向命令队列中入队一个新内核类似于在主机端向命令队列中入队一个内核。但是在OpenCL中定义了一些内建函数来确保设备队列功能的实现。我们现在就来讲解这些内建的设备队列函数。
入队新内核
OpenCL 2.0中增加了一个在内核中入队新内核的函数,如下列出了内建入队内核函数:
int enqueue_kernel (
queue_t queue,
kernel_enqueue_flags_t flags,
const ndrange_t ndrange,
void (^block)(void))
int enqueue_kernel (
queue_t queue,
kernel_enqueue_flags_t flags,
const ndrange_t ndrange,
uint num_events_in_wait_list,
const clk_event_t *event_wait_list,
clk_event_t *event_ret,
void (^block)(void))
int enqueue_kernel (
queue_t queue,
kernel_enqueue_flags_t flags,
const ndrange_t ndrange,
void (^block)(local void *, ...),
uint size0, ...)
int enqueue_kernel (
queue_t queue,
kernel_enqueue_flags_t flags,
const ndrange_t ndrange,
uint num_events_in_wait_list,
const clk_event_t *event_wait_list,
clk_event_t *event_ret,
void (^block)(local void *, ...),
uint size0, ...)
根据是否有事件参数和设置局部存储器大小,把入队内核内建函数enqueue_kernel()分为四种情况。在此我们只根据函数的传入参数名来讲解其意义,不详细区分参数属于上述哪四种情况之一,参数含义如下:
- queue:在主机端创建的设备队列(在内核中我们使用queue_t数据结构来表示),一般使用函数get_default_queue()来获得。我们可以使用如下方式在主机端创建设备队列:
cl_queue_properties props[] =
{
CL_QUEUE_PROPERTIES,
CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE
| CL_QUEUE_ON_DEVICE | CL_QUEUE_ON_DEVICE_DEFAULT,
0
};
devicequeue = clCreateCommandQueueWithProperties(context, device,
props, &err);
- f lags:标识用来决定何时执行子内核。f lag的可取值如下:
①CLK_ENQUEUE_FLAGS_NO_WAIT:子内核不需要等待父内核执行完成再执行,即子内核和父内核同时执行。对于这种情况,我们需要特别注意数据同步问题。
②CLK_ENQUEUE_FLAGS_WAIT_KERNEL:子内核需要等待父内核完全执行完再执行。
③CLK_ENQUEUE_FLAGS_WAIT_WORK_GROUP:子内核会等待父内核中入队子内核的工作组执行完再执行。
- ndrange:确定NDRange网格大小。
-
block:将会入队的子内核。
-
num_events_in_wait_list:执行子内核之前需要等待完成的事件个数。
- event_wait_list:需要等待完成的事件列表。
- event_ret:子内核执行返回的事件。
- size0:指定子内核使用的局部存储器大小。
需要说明的是父内核在设备队列中入队子内核后,主机端检测到父内核执行状态为CL_COMPLETE则说明父内核以及子内核都成功执行完成。如果子内核执行失败,则在主机端获得的父内核执行状态会是一个值为负数的错误代码。
协助函数
在父内核中,我们可以使用下表里的内建协助函数来设置执行子内核的默认命令队列,以及执行子内核的NDRange网格参数设置。
事件函数
在OpenCL应用程序中使用cl_event数据结构来表示事件对象,但是在内核代码中使用clk_event_t数据结构来表示事件对象。OpenCL提供了内建的事件函数,如下表所示。
下面的例子展示了在内核代码中使用事件对象来控制内核执行的顺序:
extern void barA_kernel(...);
extern void barB_kernel(...);
kernel void foo(queue_t q0, queue q1, ...)
{
...
clk_event_t evt0;
//入队内核到队列q0
enqueue_kernel(q0,
CLK_ENQUEUE_FLAGS_NO_WAIT,
ndrange_A,
0, NULL, &evt0,
^ {barA_kernel(...);} );
//入队内核到队列q1
enqueue_kernel(q1,
CLK_ENQUEUE_FLAGS_NO_WAIT,
ndrange_B,
1, &evt0, NULL,
^ {barB_kernel(...);} );
//释放事件evt0。队列q0中的barA_kernel完成后释放事件evt0
//入队barB_kernel到队列q1,evt0完成后barB_kernel才执行
release_event(evt0);
}
大家可能会发现,设备端的事件用法与主机端用法是一样的。两者理解了其一,便是理解了事件对象。
其他内建函数
OpenCL 2.0还包含了表4-26中的其他内建函数。
对于enqueue_marker()函数,如果入队标签命令成功则返回CLK_SUCCESS;否则返回CLK_ENQUEUE_FAILURE。如果在clCompileProgram()或clBuildProgram()中添加-g编译选项,则返回相应的具体错误原因,而不是返回CLK_ENQUEUE_FAILURE。