[OpenCL] 主机编程:基础数据结构(8)

2.7 在命令队列中收集 Kernels

  创建Kernel时,您不必识别目标设备——它可以发送到上下文中的任何设备。 相反,您在创建命令队列时识别目标设备。 然后,当您将内核部署到队列时,它们将被发送到与队列关联的设备。

  本章的重点是 kernels,但 kernels 执行只是一种可以分派到命令队列的命令。 命令是从主机发送的消息,告诉设备执行操作。 除了 kernels 执行之外,许多 OpenCL 命令操作还涉及数据传输:从设备读取数据到主机,从主机写入数据到设备,以及在设备之间复制数据。 第 3 章详细讨论了这些命令。

  图 2.2 显示了一个主机向三个设备发送命令。 如图所示,每个设备都有自己的命令队列。
在这里插入图片描述
  数据传输操作可以向设备传送数据或从设备传送数据,但命令队列中的命令仅在一个方向移动:从主机到设备。 设备不向主机发送命令。

  默认情况下,命令队列按接收顺序处理命令,但您可以在创建命令队列时更改此默认行为。 这是下一次讨论的主题。

2.7.1 创建命令队列

  在 OpenCL 中,命令队列由 cl_command_queue 结构表示,使用起来很简单。 与我们看过的其他数据结构不同,命令队列没有提供信息的函数。 此外,只有一个函数可以创建新队列。 它被称为 clCreateCommandQueue,其签名如下:

clCreateCommandQueue(cl_context context, cl_device_id device,
	cl_command_queue_properties properties, cl_int *err)

  这将返回一个 cl_command_queue,它的引用计数可以用 clRetainCommandQueue 递增,用 clReleaseCommandQueue 递减。 签名的参数很容易理解,除了第三个参数。 这必须标识 cl_command_queue_properties 枚举类型中的两个值之一:

  • CL_QUEUE_PROFILING_ENABLE – 启用分析事件
  • CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE – 启用队列命令的乱序执行

  通过设置第一个属性,您可以在队列处理其命令时接收计时事件。 第 7 章详细讨论了这个主题。 第二个属性与设备如何处理队列中的项目有关。 默认情况下,命令队列遵循先进先出 (FIFO) 原则。 例如,如果您是餐厅排队的第一个人,您将是第一个被送达的人。 同样,第一个分派到命令队列的 kernel 将首先执行。

  但是,如果您使用 CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE 属性集创建队列,目标设备将能够无序处理内核。 也就是说,设备将能够在完成之前的 kernel 之前启动其他 kernel 。 以下代码显示了如何使用此属性创建 cl_command_queue:

clCreateCommandQueue(context, device,
	CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE, &err)
2.7.2 排队kernel执行命令

  OpenCL 提供了许多以 clEnqueue 开头的函数,每个函数都通过命令队列向设备分派命令。 其中最简单的是 clEnqueueTask,它通过命令队列向设备发送 kernel 执行命令。 该函数的签名如下:

clEnqueueTask(cl_command_queue queue, cl_kernel kernel,
	cl_uint num_events, const cl_event *wait_list, cl_event *event)

  前两个论点再简单不过了。 第一个标识将命令发送到特定设备的 cl_command_queue。 第二个参数是包含要执行的 OpenCL 函数的 cl_kernel。

  一旦调用此函数,kernel 执行命令就会发送到命令队列。 您不必调用单独的函数来执行 kernel ——设备将在处理命令时执行 kernel 函数。

  下面的代码展示了它是如何工作的。 它创建一个命令队列并将命令排入队列以执行kernel。

清单 2.7 将 kernel 执行命令排入队列:queue_kernel.c

...
cl_command_queue queue;
...
// 创建命令队列
queue = clCreateCommandQueue(context, device, 0, &err);
if(err < 0) {
    perror("Couldn't create the command queue");
    exit(1);
}
// 排队kernel执行
err = clEnqueueTask(queue, kernel, 0, NULL, NULL);
if(err < 0) {
    perror("Couldn't enqueue the kernel execution command");
    exit(1);
}
// 解除分配命令队列
clReleaseCommandQueue(queue);
...

  如本清单所示,命令队列很容易在代码中使用。 但是有一个问题:queue_kernel.c 源代码可以编译,但它可能无法正常执行。 这是因为 kernel 函数没有任何参数。 您可以通过查看内核代码来看到这一点:

kernel void blank() {}

  无聊,不是吗? 没有参数,该函数没有要处理的数据。 下一章将解释如何编写主机应用程序来为内核函数设置参数。 然后,在第 4 章,我们将仔细研究内核编码。

2.8 概括

  虽然我喜欢英语,但我不得不承认它的语法很复杂。 您必须处理从句、短语、时态、语气和许多其他语法元素。 OpenCL 类似。 我很欣赏用于高性能编码的跨平台工具包的想法,但处理 OpenCL 的数据结构可能是一个令人痛苦的过程。 然而,没有办法绕过它——如果你想构建一个重要的 OpenCL 应用程序,你需要对平台、设备、上下文、程序、内核和命令队列有扎实的了解。

  本章重点介绍主机应用程序,其主要功能涉及向设备发送命令。 宿主应用程序通常从创建一个或多个 cl_platform_id 数据结构开始,每个数据结构代表一个供应商的 OpenCL 实现。 然后,使用一个或多个平台,应用程序找到连接的设备,这些设备由 cl_device_id 结构表示。 应用程序可以通过调用 clGetDeviceInfo 找到有关这些设备的信息,一旦确定了目标设备,它就会将它们组合到一个 cl_context 中。

  接下来,应用程序读入源代码或二进制代码,其中包含专门标记的函数,称为kernel函数。 它使用此代码形成 cl_program,然后使用 clBuildProgram 构建程序。 这将为上下文中的每个设备编译代码,一旦程序构建成功,主机应用程序就会为其中包含的函数创建 cl_kernels。

  要启用与设备的通信,主机应用程序会创建一个 cl_command_queue。 它将命令分派到这个队列中,每个命令告诉目标设备执行一个操作。 例如,clEnqueueTask 将内核函数发送到设备执行。 其他命令告诉设备将数据传输到主机或从主机传输数据。

  主机开发是一个复杂的话题,如果讨论还没有意义,不要担心。 随着您检查更多代码并开始编写自己的代码,处理这些数据结构将成为第二天性。

  本章解释了如何编写将内核函数分派给设备的主机应用程序。 但在实际应用中,您需要将数据传送到连接的设备。 解释 OpenCL 数据传输和分区需要一整章,我们接下来会看这个。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值