OpenCL 平台模型 - 执行模型 - 内存模型 - 编程模型

OpenCL 平台模型 - 执行模型 - 内存模型 - 编程模型

面向异构平台的应用需要完成以下步骤:

  1. 发现构成异构系统的组件。
  2. 探查这些组件的特征,使软件能够适应不同硬件单元的特定特性。
  3. 创建将在平台上运行的指令块 (内核)。
  4. 建立并管理计算中涉及的内存对象。
  5. 在系统中正确的组件上按正确的顺序执行内核。
  6. 收集最终结果。

上述步骤通过 OpenCL 中的一系列 API 再加上一个面向内核的编程环境来完成,我们把问题分解为以下模型:

  • 平台模型 (platform model):异构系统的高层描述。
  • 执行模型 (execution model):指令流在异构平台上执行的抽象表示。
  • 内存模型 (memory model):OpenCL 中的内存区域集合以及一个 OpenCL 计算期间这些内存区域如何交互。
  • 编程模型 (programming model):程序员设计算法来实现一个应用时使用的高层抽象。

1. 平台模型 (platform model)

OpenCL 平台模型定义了使用 OpenCL 的异构平台的一个高层表示。OpenCL 平台总是包括一个宿主机 (host),宿主机与 OpenCL 程序外部的环境交互,包括 I/O 或与程序用户的交互。

宿主机与一个或多个 OpenCL 设备 (device) 连接。设备就是执行指令流 (或内核) 的地方,OpenCL 设备通常称为计算设备 (compute device),可以是 CPU、GPU、DSP 或硬件提供以及 OpenCL 开发商支持的任何其他处理器。

在这里插入图片描述
在这里插入图片描述
OpenCL 平台模型包括一个宿主机以及一个或多个 OpenCL 设备。各个 OpenCL 设备有一个或多个计算单元,其中各个计算单元包括一个或多个处理单元。

OpenCL 设备进一步划分为计算单元 (Compute Units),而计算单元还可以更进一步划分为一个或多个处理单元 (Processing Elements),设备上的计算都在处理单元 (Processing Elements) 中完成。

2. 执行模型 (execution model)

OpenCL 应用由两个不同部分组成:一个宿主机程序 (host program) 以及一个或多个内核 (kernel) 组成的集合。宿主机程序在宿主机上运行。OpenCL 并没有定义宿主机程序如何工作的具体细节,只是定义了它与 OpenCL 中定义的对象如何交互。

内核在 OpenCL 设备上执行,它们完成 OpenCL 应用的具体工作。内核通常是一些简单的函数,将输入内存对象转换为输出内存对象。OpenCL 定义了两类内核:

  • OpenCL 内核:用 OpenCL C 编程语言编写并用 OpenCL 编译器编译的函数。所有 OpenCL 实现都必须支持 OpenCL 内核。
  • 原生内核:OpenCL 之外创建的函数,在 OpenCL 中可以通过一个函数指针来访问。这些函数可以是宿主机源代码中定义的函数,或者是从一个专用库导出的函数。执行原生内核的能力是 OpenCL 的一个可选功能,原生内核的语义依赖于具体实现。

2.1 内核在 OpenCL 设备上执行

内核在宿主机上定义。宿主机程序发出一个命令,提交内核在一个 OpenCL 设备上执行。由宿主机发出这个命令时,OpenCL 运行时系统会创建一个整数索引空间。对应这个索引空间中的各个点将分别执行内核的一个实例。我们将执行内核的各个实例称为一个工作项 (work-item),工作项由它在索引空间中的坐标来标识。这些坐标就是工作项的全局 ID。

提交内核执行的命令相应地会创建一个工作项集合,其中各个工作项使用内核定义的同样的指令序列。尽管指令序列是相同的,但是由于代码中的分支语句或者通过全局 ID 选择的数据可能不同,因此各个工作项的行为可能不同。

工作项组织为工作组 (work-group)。工作组提供了对索引空间更粗粒度的分解,跨越整个全局索引空间。工作组在相应维度的大小相同,这个大小可以整除各维度中的全局大小。为工作组指定一个唯一的 ID,这个 ID 与工作项使用的索引空间有相同的维度。为工作项指定一个局部 ID,这个局部 ID 在工作组中是唯一的,这样就能由其全局 ID 或者由其局部 ID 和工作组 ID 唯一地标识一个工作项。

给定工作组中的工作项会在一个计算单元的处理单元上并发执行,这是理解 OpenCL 并发性的关键。具体实现可能串行化内核的执行,甚至可能在一个内核调用中串行化工作组的执行。OpenCL 只能确保一个工作组中的工作项并发执行 (和共享设备上的处理器资源)。不要认为工作组或内核调用会并发执行。尽管实际上它们通常确实会并发执行,但是算法设计人员不能依赖这一点。

索引空间是一个 N 维的值网格,称为 NDRange。这个 N 维索引空间中的 N 可以是 123在一个 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(Gx1),0(Gy1)]

将这个 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×GyNDRange 索引空间将划分为 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
L x = G x / W x L y = G y / W y \begin{aligned} L_{x} = G_{x} / W_{x} \\ L_{y} = G_{y} / W_{y} \\ \end{aligned} Lx=Gx/WxLy=Gy/Wy

可以根据工作项的全局 ID ⁡ ( g x , g y ) \operatorname{ID}(g_{x}, g_{y}) ID(gx,gy) 来定义一个工作项,或者结合局部 ID ⁡ ( l x , l y ) \operatorname{ID} (l_{x}, l_{y}) ID(lx,ly) 和工作组 ID ⁡ ( w x , w y ) \operatorname{ID}(w_{x}, w_{y}) ID(wx,wy) 来定义:
g x = w x ∗ L x + l x g y = w y ∗ L y + l y \begin{aligned} g_{x} = w_{x} * L_{x} + l_{x} \\ g_{y} = w_{y} * L_{y} + l_{y} \\ \end{aligned} gx=wxLx+lxgy=wyLy+ly

还可以由 g x g_x gx g y g_y gy 后退一步,恢复局部 ID ⁡ \operatorname{ID} ID 和工作组 ID ⁡ \operatorname{ID} ID
w x = g x / L x w y = g y / L y l x = g x % L x l y = g y % L y \begin{aligned} w_{x} = g_{x} / L_{x} \\ w_{y} = g_{y} / L_{y} \\ l_{x} = g_{x} \% L_{x} \\ l_{y} = g_{y} \% L_{y} \\ \end{aligned} wx=gx/Lxwy=gy/Lylx=gx%Lxly=gy%Ly

在这些公式中,使用了整除 (截断除) 和取模或取余 (%) 操作。

在所有这些公式中,假设索引空间的各个维度都从 0 开始。在 OpenCL 1. 1 中可以为全局索引空间的起始点定义一个偏移量。需要为各个维度定义偏移量,因为它修改了全局索引,所以使用一个小写字母 o o o 表示偏移量。对于非 0 偏移量 ( o x , o y ) (o_x, o_y) (ox,oy),连接全局和局部索引的最后公式为:
g x = w x ∗ L x + l x + o x g y = w y ∗ L y + l y + o y \begin{aligned} g_x = w_x * L_x + l_x + o_x \\ g_y = w_y * L_y + l_y + o_y \\ \end{aligned} gx=wxLx+lx+oxgy=wyLy+ly+oy

在上图中,各个小方块分别是一个工作项,各个维度中使用默认偏移量 0。阴影方块的全局 ID ⁡ \operatorname{ID} ID ( g x , g y ) = ( 6 , 5 ) (g_x, g_y) = (6, 5) (gx,gy)=(6,5),落在 ID ⁡ ( 1 , 1 ) \operatorname{ID}(1, 1) ID(1,1) 的工作组中,局部 ID ⁡ \operatorname{ID} ID ( 2 , 1 ) (2, 1) (2,1)

2.2 上下文

OpenCL 应用的计算工作在 OpenCL 设备上进行,内核在宿主机上定义的,而且宿主机为内核建立了上下文。宿主机定义了 NDRange 和队列,队列将控制内核如何执行以及何时执行的细节。

上下文由宿主机使用 OpenCL API 函数创建和管理,宿主机的第一个任务是为 OpenCL 应用定义上下文,上下文定义了一个环境,内核在这个环境中定义和执行。以下资源定义上下文:

  • 设备 (device):宿主机使用的 OpenCL 设备集合。
  • 内核 (kernel):在 OpenCL 设备上运行的 OpenCL 函数。
  • 程序对象 (program object):实现内核的程序源代码和可执行文件。
  • 内存对象 (memory object):内存中对 OpenCL 设备可见的一组对象,包含可以由内核实例处理的值。

上下文中包括一个或多个程序对象 (program object),程序对象包含内核的代码。程序对象可以想象成一个动态库,可以从中取出内核使用的函数。

OpenCL 程序员编写 OpenCL 应用程序,但是无法控制最终用户在哪里运行应用程序 (可能是GPU、CPU 或者其他芯片)。OpenCL 程序员所知道的只是目标平台符合 OpenCL 规范。解决这个问题的方法就是在运行时从源代码构建程序对象。宿主机程序定义上下文中的设备,只有那时才有可能知道如何编译程序源代码来创建内核代码。

程序对象会在运行时由宿主机程序构建,在运行时从源代码构建程序对象。对于源代码本身,OpenCL 在形式上相当灵活。在很多情况下,这会是一个常规的字符串,可以在宿主机程序中静态定义,或者在运行时从一个文件加载,也可能在宿主机程序中动态生成。

上下文中包含 OpenCL 设备和一个程序对象,将从这个程序对象中取出内核来执行。内存对象在宿主机上明确定义,并在宿主机和 OpenCL 设备之间移动。对于程序员来说,这确实増加了负担,但是这样允许我们支持更多的平台。

上下文就是 OpenCL 设备、程序对象、内核以及内核在执行时使用的内存对象。

2.3 命令队列

宿主机与 OpenCL 设备之间的交互是通过命令完成的,这些命令由宿主机提交给命令队列 (command-queue)。这些命令会在命令队列中等待,直到在 OpenCL 设备上执行。命令队列由宿主机创建,并在定义上下文之后关联到一个 OpenCL 设备。宿主机将命令放入命令队列,然后调度这些命令在关联设备上执行。OpenCL 支持 3 种类型的命令:

  • 内核执行命令 (kernel execution command):在 OpenCL 设备的处理单元上执行内核。
  • 内存命令 (memory command):在宿主机和不同内存对象之间传递数据,在内存对象之间移动数据,或者将内存对象映射到宿主机地址空间,或者从宿主机地址空间解映射。
  • 同步命令 (synchronization command):对命令执行的顺序施加约束。

内存对象从宿主机移到设备上,内核参数关联到内存对象,然后提交到命令队列执行。内核完成工作时,计算中生成的内存对象可能会再复制到宿主机。将多个内核提交到队列时,它们可能需要交互。例如一组内核能生成内存对象,而另一组内核需要这些内存对象来完成处理。在这种情况下,可以使用同步命令来强制第一组内核先完成,在此之前另一组内核不能开始。命令总是与宿主机程序异步执行。宿主机程序向命令队列提交命令,然后继续工作,而不必等待命令完成。如果有必要让宿主机等待一个命令,可以利用一个同步命令显式地建立这个约束。

一个队列中的命令执行时可以有以下两种模式:

  • 有序执行 (in-order execution):命令按其在命令队列中出现的顺序发出,并按顺序完成。队列中前一个命令完成之后,下一个命令才会开始。这会将队列中命令的执行顺序串行化。
  • 乱序执行 (out-of-order execution):命令按顺序发出,但是下一个命令执行之前不会等待前一个命令完成。程序员要通过显式的同步机制来强制顺序约束。

所有 OpenCL 平台都支持有序模式,而乱序模式是可选的。对于一个应用来说,在所有内核完成之前它是无法完成的。要得到一个尽可能降低运行时开销的高效程序,肯定希望所有计算单元都得到充分利用,另外运行的时间大致相同。仔细考虑向队列提交命令的顺序,使有序执行能够达到很好的负载平衡。乱序队列命令可以按任意顺序执行,如果一个计算单元很早就完成了它的工作,就可以立即从命令队列获取一个新命令,开始执行一个新内核,这称为自动负载平衡 (automatic load balancing)

如果一个应用程序中有多个执行流,就有可能出现灾难。有可能还没有写入数据就意外地要使用这个数据,或者内核执行的顺序可能导致错误的答案。可以用一个同步命令告诉一组内核等待,直到之前的一组命令完成。这通常很有效,但是在有些情况下还需要更复杂的同步协议。

为了支持定制的同步协议,提交到命令队列的命令会生成事件对象。可以告诉一个命令等待,直到事件对象上的某些条件成立时才执行。这些事件还可以用来协调宿主机和 OpenCL 设备之间的执行。

还可以通过一个上下文为 OpenCL 设备关联多个队列。两个队列并发、独立地运行,OpenCL 中对它们之间如何同步没有明确的机制。

3. 内存模型 (memory model)

OpenCL 定义了两种类型的内存对象:缓冲区对象 (buffer object) 和图像对象 (image object)。缓冲区对象就是内核可用的一个连续的内存区,程序员可以将数据结构映射到这个缓冲区,并通过指针访问缓冲区。

图像对象仅限于存储图像。图像存储格式可以进行优化来满足一个特定 OpenCL 设备的需要。图像内存对象是一个不透明的对象。OpenCL 框架提供了很多函数来管理图像,但是除了这些特定的函数外,图像对象的内容对内核程序是隐藏的。

OpenCL 还允许程序员将一个内存对象的子区域指定为不同的内存对象。这使得一个大的内存对象的子区域也成为 OpenCL 中的首类对象,可以通过命令 队列管理和协调。

OpenCL 内存模型定义了 5 种不同的内存区域:

  • 宿主机内存 (host memory):这个内存区域只对宿主机可见,OpenCL 只定义了宿主机内存与 OpenCL 对象和构造如何交互。
  • 全局内存 (global memory):这个存储区域允许读、写所有工作组中的所有工作项。工作项可以读、写全局内存中一个内存对象的任何元素。读、写全局内存可能会缓存,这取决于设备的容量。
  • 常量内存 (constant memory):全局内存的这个内存区域在执行一个内核期间保持不变。宿主机分配并初始化放在常量内存中的内存对象,这些对象对于工作项是只读的。
  • 局部内存 (local memory):这个内存区域对工作组是局部的。这个内存区域可以用来分配由该工作组中所有工作项共享的变量。它可以实现为 OpenCL 设备上的专用内存区域。局部内存区域也可以映射到全局内存的区段 (section)。
  • 私有内存 (private memory):这个内存区域是一个工作项私有的区域,一个工作项私有内存中定义的变量对其他工作项不可见。

在这里插入图片描述
在这里插入图片描述

工作项在处理单元上运行,有其自己的私有内存。工作组在一个计算单元上运行,与该组中的工作项共享一个局部内存区域。OpenCL 设备内存利用宿主机来支持全局内存。

在大多数情况下,宿主机和 OpenCL 设备内存模型是独立的。在有些情况下,它们确实需要交互。宿主机和 OpenCL 设备交互有两种方式:显式地复制数据,或者映射和解映射内存对象的内存区域。

要显式地复制数据,宿主机将命令入队,在内存对象和宿主机内存之间传输数据。这些内存传输命令可以是阻塞的,也可能是非阻塞的。一旦宿主机上关联的内存资源可以安全地重用,要求阻塞内存传输的 OpenCL 函数就会返回。对于非阻塞内存传输,一旦命令入队,OpenCL 函数就会立即返回,不论宿主机内存是否可以安全使用。

实现宿主机与 OpenCL 内存对象之间交互的映射/解映射方法允许宿主机将一个区域从内存对象映射到它自己的地址空间。内存映射命令在命令队列屮排队,可以是阻塞的,也可以是非阻塞的。一旦从内存对象映射了一个区域,宿主机就可以读、写这个区域。宿主机完成这个映射区域的访问 (读、写) 时,宿主机可以解除这个区域的映射。

OpenCL 并没有在宿主机上规定内存一致性 (memory consistency) 模型。先从离宿主机最远的内存 (私有内存区域) 开始考虑,逐步转向宿主机。私有内存对宿主机是不可见的,它只对相应的工作项可见。这个内存采用顺序编程中很熟悉的加载/存储内存模型。对私有内存的加载和存储不能重新排序,即除了程序文本中定义的顺序外,不能以其他顺序出现。

对于局部内存,可以保证一个工作组中一组工作项能够看到的值在工作组同步点是一致的。一个工作组栅栏 (work-group barrier) 要求在栅栏之前定义的所有加载和存储必须先完成,工作组中这个栅栏之后的工作项才能继续。栅栏标记了一组工作项执行中的某一点,在这一点可以保证内存是一致的,在继续执行之前处于已知状态。

由于局部内存只是在一个工作组内共享,所以这对于定义局部内存区域的内存一致性就足够了。对于一个工作组中的工作项,在工作组栅栏,全局内存也是一致的。尽管这个内存在工作组间共享,但无法强制执行一个内核的不同工作组之间全局内存的一致性。

对于内存对象,OpenCL 定义了一个宽松一致性模型。单个工作项在内存中看到的值不能保证任何时刻在整个工作项集中都保持一致。在给定的时刻,对于不同的工作项,对 OpenCL 内存对象的加载和存储可能以不同的顺序出现。这称为宽松一致性 (relaxed consistency) 模型,它没有我们期望的加载/存储模型那么严格,即并发执行要与串行执行的顺序完全匹配。

与一个内核相关联的所有工作项完成时,这个内核释放的内存对象的相应加载和存储完成后,内核命令才能标志为完成。对于有序队列,这足以定义内核间的内存一致性。对于乱序队列,则有两个选择 (称为同步点)。第一个选择是在特定的同步点 (如命令队列栅栏) 强制一致性。第二个选择是通过事件机制显式地管理一致性。这些选择同样用于强制宿主机和 OpenCL 设备之间的一致性,内存仅在命令队列中的同步点是一致的。

4. 编程模型 (programming model)

OpenCL 定义了两种不同的编程模型:任务并行和数据并行。还有一种混合模型:包含数据并行的任务并行。

4.1 数据并行编程模型

适合采用数据并行编程模型的问题都与数据结构有关,这些数据结构的元素可以并发更新。基本上,就是将一个逻辑指令序列并发地应用到数据结构的元素上。并行算法的结构被设计为一个序列,即对问题领域中数据结构并发更新的序列。

关键是执行一个内核时定义的 NDRange,算法设计者要保证问题中的数据结构与 NDRange 索引空间一致,将它们映射到 OpenCL 内存对象。内核定义了 OpenCL计算中作为工作项并发应用的指令序列。

在更复杂的数据并行问题中,一个工作组中的工作项需要共享数据。这要通过存储在局部内存区域中的数据来支持。只要工作项之间引入了依赖性,就必须特别当心,不论工作项以什么顺序完成,都要生成相同的结果。工作项的执行需要同步,一个工作组中的工作项可能参与一个工作组栅栏。一个工作组中的所有工作项必须先执行这个栅栏,之后才允许跨过这个栅栏继续执行。要么工作组中执行内核的所有工作项都遇到工作组栅栏,要么所有工作项都不会遇到这个栅栏。

对于执行一个内核时不同工作组的工作项之间如何同步,OpenCL 1.1 并未提供任何机制。 这是程序员设计并行算法时一定要记住的一个重要限制。

归约是指一个数据元素集合通过某种结合运算归约为一个元素。最常见的例子就是求和或者査找一组数据元素中的极值 (最大值或最小值)。在归约中,工作项要完成一个计算来生成将要归约的数据元素。这必须在所有工作项上完成,之后才能对所有工作项完成累积,生成工作项的一个子集 (通常是一个大小为 1 的子集)。

OpenCL 提供了层次结构的数据并行性:工作组中工作项的数据并行再加上工作组层次的数据并行。OpenCL 规范讨论了这种数据并行形式的两个变种。在显式模式 (explicit model) 中,程序员负责显式地定义工作组的大小。利用第二个模型,即隐式模型 (implicit model),程序员只需定义 NDRange 空间,由系统选择工作组。

如果内核不包含任何分支语句,那么各个工作项会执行相同的操作,但是会处理其全局 ID 选择的数据项的一个子集。这种情况定义了数据并行模型的一个重要子集,称为单指令多数据 (Single Instruction Multiple Data, SIMD)。内核中的分支语句可能让各个工作项执行完全不同的操作。尽管各个工作项使用相同的程序 (内核),但它完成的具体工作可能完全不同。这通常称为单程序多数据 (Single Program Multiple Data, SPMD) 模型。

OpenCL 同时支持 SIMD 和 SPMD 模型。在指令内存带宽有限的平台上,或者处理单元映射到一个矢量单元,SIMD 模型会更为高效。数据并行很自然地切合了 OpenCL 执行模型,这个模型是层次结构,因为数据并行计算 (工作项) 可能包括矢量指令 (SIMD) 作为更大规模的块级数据并行 (工作组) 的一部分。

4.2 任务并行编程模型

OpenCL 执行模型被设计为以数据并行作为主要目标,这个模型还支持大量任务并行算法。OpenCL 将任务定义为单个工作项执行的内核,而不考虑 OpenCL 应用中其他内核使用的 NDRange如果程序员所希望的并发性来自于任务,就会使用这个模型。并发性可能只是通过矢量类型上的矢量操作来表述,或者任务可能使用原生内核接口定义的一个内核,并行性使用 OpenCL 之外的一个编程环境来表述。

任务并行的另一个版本是内核作为任务提交,利用一个乱序队列同时执行这些任务。OpenCL 应用可以将所有任务入队,由计算单元动态地调度工作。任务数远大于计算单元数时,这个策略将是一个很有效的方法,可以得到很好的负载平衡。这种类型的任务并行不一定适用所有 OpenCL 平台,因为命令队列的乱序模式是 OpenCL 1. 1 中的一个可选特性。

任务并行的第三个版本是任务使用 OpenCL 的事件模型连接到一个任务图。提交到事件队列的命令有可能生成事件,后续的命令在执行之前可能要等待这些事件。与支持乱序执行模型的命令队列结合使用时,就允许 OpenCL 程序员在 OpenCL 中定义静态任务图,图中的节点表示任务,边为节点之间的依赖关系 (由事件管理)。

4.3 并行算法限制

OpenCL 框架为数据并行和任务并行编程模型定义了一个坚实的基础,OpenCL 优秀的可移植性是以算法中所能支持的通用性为代价的。问题的关键要归结于执行模型中所做的假设,提交一个命令来执行一个内核时,我们可以只假设工作组中的工作项并发执行。具体实现可以按任何顺序运行不同的工作组 - 包括串行 (一个接一个地运行)。即使启用乱序队列模式,符合规范的实现仍有可能串行地执行内核。

首先,考虑与一个内核执行关联的工作组集合。符合规范的 OpenCL 实现可以采用它选择的任何方式对这些工作组排序。我们构造算法时不能依赖于执行一个内核的相关工作组间共享数据的具体细节,这是不安全的。

其次,考虑多个内核的执行顺序。它们按其入队的顺序提交执行,但是可以串行执行 (有序命令队列模式) 或并发执行 (乱序命令队列模式)。即使采用乱序队列,实现也完全可以采用串行顺序执行内核。前面的内核如果等待来自后面内核的事件,就可能死锁。与一个算法关联的任务图只能有单向边,从命令队列中先入队的节点指向命令队列中后入队的内核。

这说明存在一些无法用 OpenCL 表述的并行设计模式。对现在来说,别无他法,只能接受这些限制。

4.4 其他编程模型

程序员可以结合 OpenCL 的编程模型来创建各种复合编程模型。例如混合模型,一个数据并行算法中的工作项通过矢量指令包含 SIMD 并行。OpenCL 通过一个可移植的平台模型和一个强大的执行模型公布硬件,这些工作结合起来定义了一个灵活的硬件抽象层,计算机科学家可以在 OpenCL 硬件抽象层上增加其他编程模型层。

References

(美) Aaftab Munshi, (美) Benedict R. Gaster, (美) Timothy G. Mattson, (美) James Fung, (美) Dan Ginsburg 著, 苏金国, 李璜, 杨健康 译. OpenCL 编程指南. 机械工业出版社, 2012.
http://www.heterogeneouscompute.org/?page_id=5
http://www.heterogeneouscompute.org/?page_id=7
https://github.com/bgaster/opencl-book-samples
https://www.nersc.gov/assets/pubs_presos/MattsonTutorialSC14.pdf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yongqiang Cheng

梦想不是浮躁,而是沉淀和积累。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值