前言

一、什么是OpenCL?

OpenCL是面向由CPU、GPU和其他处理器组合构成的计算机进行编程的行业标准框架。用OpenCL, 可以编写一款能够在各类系统上成功运行的程序,这些系统包括移动电话、笔记本电脑, 甚至是大规模超计算机中的节点。Open CL通过公布硬件 来提供高度的可移植性, 而不是将硬件隐藏在精巧的抽象之下。这 说明OpenCL程序员必须显式地定义平台、 上下文, 以及在不同设备上调度工作。
OpenCL支持大量不同类型的应用。 笼统概括这些应用是很困难的。 不过, 不论哪一种情况, 面向异构平台的应用都必须完成以下步骤:

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

这些步骤通过OpenCL中的一系列API再加上一个面向内核的编程环境来完成。 采取分而治之的策略解释以上所有步骤的工作,分成以下模型:

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

1.1 平台模型

平台模型定义了使用OpenCL的异构平台的一个高层表示。如下图所示,OpenCL平台总是包括一个宿主机(host)。 宿主机与 一 个或多个OpenCL设备连接,设备就是执行指令流(或内核) 的地方, 因此, OpenCL设备通常称计算设备。设备可以是 CPU、GPU、DSP或硬件提供以及OpenCL开发商支持的任何其他处理器。

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

1.2 执行模型

OpenCL应用由两个不同部分组成:一个宿主机程序+一个或者多个内核(kernel)组成的集合。宿主机程序在宿主机上运行,内核在OpenCL设备上执行。内核完成OpenCL的具体工作,通常是一些简单的函数,将输入内存对象转换为输出内存对象,OpenCL定义了两类内核:

  • OpenCL内核: 用 OpenCL C 编程语言编写并用 OpenCL 编译器编译的函数。所有 OpenCL 实现都必须支持 OpenCL内核。
  • 原生内核: OpenCL 之外创建的函数, 在 OpenCL中可以通过一个函数指针来访问。如, 这些函数可以是宿主机师、代码中定义的函数。

执行模型指出了内核如何执行, 它们与宿主机如何交互,以及它们与其他内核如何交互。是OpenCL的基础与重点。

1.2.1 内核如何在 OpenCL 设备上执行

内核定义在宿主机上,宿主机程序发出命令,提交内核在一个OpenCL设备上执行。OpenCL运行时会创建一个整数索引空间,索引坐标对应执行内核的一个实例,各个执行内核的实例称为一个工作项,其对应的坐标就是工作项的全局ID。提交内核执行的命令相应地会创建一个工作项集合, 其中各个工作项使用内核定义的同样的指令序列。尽管指令序列是相同的, 但是由于代码中的分支语句或者通过全局ID选择的数据 可能不同, 因此各个工作项的行为可能不同。
工作项组织为工作组。工作组提供了对索引空间更粗粒度的分解, 跨越整个全局索引空间。工作项可由自身的全局ID或者工作组ID+其局部ID唯一标识。给定工作组中的工作项会在一个计算单元的处理单元上并发执行。
索引空间是一个N维的值网格,也称NDRange,下图给出一个具体的索引空间的例子:
在这里插入图片描述

1.2.2 上下文

虽然OpenCL应用的计算工作是在OpenCL设备上进行的,但是,宿主机定义了内核,而且为内核的建立创建了上下文,定义了NDRange和队列(队列存储了内核如何执行以及何时执行的细节),所有这些重要函数都包含在OpenCL定义的API中。
宿主机的第一个任务是为 OpenCL应用定义上下文。顾名思义, 上下文定义了一个环境, 内核就在这个环境中定义和执行。更准确地说, 由以下资源定义上下文:

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

1.2.3 命令队列

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

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

一个典型的宿主机程序,需要定义上下文、命令队列、内存、程序对象和宿主机所需要的数据结构。内存对象从宿主机转移到设备上,内核参数关联到内存对象,然后提交到命令队列执行。内核完成工作时,计算中生成的内存对象可能会再复制到宿主机。

1.3 内存模型

OpenCL 定义了两种类型的内存对象:

  • 缓冲区对象(buffer object ):内核可用的一个连续的内存区。程序员可以将数据结构映射到这个缓冲区, 并通过指针访问缓冲区。
  • 图像对象(image object): 仅限于存储图像。图像存储格式可以进行优化来满足一个特定OpenCL设备的需要。因此, OpenCL要提供实现的自由,允许定制图像格式, 这很重要。

了解内存对象本身只是第一步。我们还需要理解控制 OpenCL 程序中如何使用内存对象的抽象机制。OpenCL 内存模型定义了5 种不同的内存区域:

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

内存模型如下:
在这里插入图片描述

1.4 编程模型

OpenCL 定义了两种不同的编程模型:任务并行和l数据并行。

  • 数据并行编程模型
    适合采用数据并行编程模型的问题都与数据结构有关, 这些数据结构的元素可以并发更新。
    OpenCL提供了层次结构的数据并行性:工作组中工作项的数据并行再加上工作组层次的数据并行。OpenCL规范讨论了这种数据并行形式的两个变种。在显式模式( explicit model) 中, 程序员负责显式地定义工作组的大小。 利用第二个模型, 即隐式模型( implicit model), 程序员只需定义NDRange空间, 由系统选择工作组。
    总之, 数据并行很自然地切合了OpenCL执行模型。 这个模型是层次结构, 因为数据并行 计算(工作项)可能包括矢量指令(SIMD,作为更大规模的块级数据并行(工作组)的一部分。所有这些工作结合起来为表述数据并行算法创建了一个很好的环境。

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

二、OpenCL的内容

OpenCL框架可以划分为以下组成部分:

  • OpenCL平台API:平台API定义了宿主机程序发现OpenCL设备所用的函数以及这些函数的功能,另外还定义了为OpenCL应用创建上下文的函数。
  • OpenCL运行时API:这个APl管理上下文来创建命令队列以及运行时发生的其他操作。例如,将命令提交到命令队列的函数就来自OpenCL运行时API。
  • OpenCL编程语言:这是用来编写内核代码的编程悟言。它基于ISOC99标准的一个扩展子集,因此通常称为OpenCL C编程语言。

下面对应用在 OpenCL 框架中的基本工作流做个小结:
在这里插入图片描述
首先是一个定义上下文的宿主机程序。 图中的上下文包含两个 OpenCL 设备:一个CPU设备,一个GPU设备。接下来定义了命令队列。 这里有两个队列 ,一个是面向GPU的有序命令队列,另一个是面向CPU的乱序命令队列。然后宿主机程序定义一个程序对象,这个程序对象编译后将为两个 OpenCL 设备(CPU 和 GPU )生成内核。 接下来宿主机程序定义程序所需的内存对象, 并把它们映射到内核的参数。 最后, 宿主机程序将命令放入命令队列来执行这些内核。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐