面向异构平台的应用需要完成以下步骤:
(1) 发现构成异构系统的组件。
(2) 探查这些组件的特征,使软件能够适应不同硬件单元的特定特性。
(3) 创建将在平台上运行的指令块 (内核)。
(4) 建立并管理计算中涉及的内存对象。
(5) 在系统中正确的组件上按正确的顺序执行内核。
(6) 收集最终结果。
这些步骤通过 OpenCL 中的一系列 API 再加上一个面向内核的编程环境来完成,我们把问题分解为以下模型:
OpenCL 平台模型定义了使用 OpenCL 的异构平台的一个高层表示。OpenCL 平台总是包括一个宿主机 (host)。宿主机与 OpenCL 程序外部的环境交互,包括 I/O 或与程序用户的交互。
宿主机与一个或多个 OpenCL 设备 (OpenCL device) 连接。设备就是执行指令流 (或内核) 的地方,OpenCL 设备通常称为计算设备 (compute device)。设备可以是 CPU、GPU、DSP 或硬件提供以及 OpenCL 开发商支持的任何其他处理器。
OpenCL 平台模型包括一个宿主机以及一个或多个 OpenCL 设备。各个 OpenCL 设备有一个或多个计算单元,其中各个计算单元包括一个或多个处理单元。
OpenCL 设备进一步划分为计算单元 (compute unit),而计算单元还可以更进一步划分为一个或多个处理单元。设备上的计算都在处理单元中完成。
OpenCL 应用由两个不同部分组成:一个宿主机程序 (host program) 以及一个或多个内核 (kernel) 组成的集合。宿主机程序在宿主机上运行。OpenCL 并没有定义宿主机程序如何工作的具体细节,只是定义了它与 OpenCL 中定义的对象如何交互。
内核在 OpenCL 设备上执行。它们完成 OpenCL 应用的具体工作。内核通常是一些简单的函数,将输入内存对象转换为输出内存对象。OpenCL 定义了两类内核:
内核在宿主机上定义。宿主机程序发出一个命令,提交内核在一个 OpenCL 设备上执行。由宿主机发出这个命令时,OpenCL 运行时系统会创建一个整数索引空间。对应这个索引空间中的各个点将分别执行内核的一个实例。我们将执行内核的各个实例称为一个工作项 (work-item),工作项由它在索引空间中的坐标来标识。这些坐标就是工作项的全局 ID。
提交内核执行的命令相应地会创建一个工作项集合,其中各个工作项使用内核定义的同样的指令序列。尽管指令序列是相同的,但是由于代码中的分支语句或者通过全局 ID 选择的数据可能不同,因此各个工作项的行为可能不同。
工作项组织为工作组 (work-group)。工作组提供了对索引空间更粗粒度的分解,跨越整个全局索引空间。工作组在相应维度的大小 相同,这个大小可以整除各维度中的全局大小。为工作组指定一个唯一的 ID,这个 ID 与工作项使用的索引空间有相同的维度。另外为工作项指定一个局部 ID,这个局部 ID 在工作组中是唯一的,这样就能由其全局 ID 或者由其局部 ID 和工作组 ID 唯一地标识一个工作项。
给定工作组中的工作项会在一个计算单元的处理单元上并发执行。这是理解 OpenCL 并发性的关键。具体实现可能串行化内核的执行,甚至可能在一个内核调用中串行化工作组的执行。OpenCL 只能确保一个工作组中的工作项并发执行 (和共享设备上的处理器资源)。因此,不要认为工作组或内核调用会并发执行。尽管实际上它们通常确实会并发执行,但是算法设计人员不能依赖这一点。
索引空间是一个 N
维的值网格,因此也称为 NDRange。这个 N
维索引空间中的 N
可以是 1、2 或 3。在一个 OpenCL 程序中,NDRange 由一个长度为 N
的整数数组定义,N
指定索引空间各维度的大小。各个工作项的全局和局部 ID 都是一个 N
维元组。在最简单的情况下,全局 ID 中各个值的取值范围从 0 开始,到该维度元素个数减 1。
与为工作项指定 ID 类似,仍采用这种方法为工作组指定 ID。有一个长度为 N
的数组定义各个维度中工作组的个数。工作项指定到一个工作组,并给定一个局部 ID,这个局部 ID 中各个值的取值范围也是从 0 开始,到该维度中工作组个数减 1。因此,通过结合工作组 ID 和工作组中的局部 ID 就可以唯一地定义一个工作项。
可以考虑一个 2 维的 NDRange。我们使用小写字母 g g g 表示给定下标 x x x 或 y y y 时各维度中一个工作项的全局 ID。大写字母 G G G 指示索引空间各维度的大小。因此,各工作项在全局 NDRange 索引空间中有一个坐标 ( g x , g y ) (g_x, g_y) (gx,gy),全局索引空间的大小为 ( G x , G y ) (G_x, G_y) (Gx,Gy),工作项坐标取值范围为 [ 0 … ( G x − 1 ) , 0 … ( G y − 1 ) ] [0 … (G_x - 1), 0 … (G_y - 1)] [0…(Gx−1),0…(Gy−1)]。
将这个 NDRange 索引空间划分为工作组。根据前面所述的约定,使用小写字母 w w w 表示工作组 ID,大写字母 W W W 表示各个维度中工作组的个数。维度仍用下标 x x x 和 y y y 标记。
OpenCL 要求各个维度中工作组的数目能够整除 NDRange 索引空间各个维度的大小。这样可以保证所有工作组都是满的,而且大小相同。各个方向 (在这个 2 维的例子中,就是 x x x 和 y y y 方向) 的工作组大小要用来为各个工作项定义一个局部索引空间。我们把一个工作组内的索引空间称为局部索引空间 (local index space)。按照前面使用大小写字母的约定,局部索引空间中各个维度 ( x x x 和 y y y) 的大小用大写字母 L L L 表示,工作组中的局部 ID 使用小写字母 l l l 表示。
因此,大小为 G x × G y G_x \times G_y Gx×Gy 的 NDRange 索引空间将划分为 W x × W y W_x \times W_y Wx×Wy 空间上的工作组,其索引为 ( w x , w y ) (w_x, w_y) (wx,wy),各个工作组的大小为 L x × L y L_x \times L_y Lx×Ly。