2008年,苹果公司向Khronos Group提交了一份关于跨平台计算框架的草案,该草案由苹果公司开发,并与AMD、IBM、Intel和NVIDIA公司合作逐步晚上。这个跨平台计算框架就是OpenCL。20088年12月8日,OpenCL1.0技术规范发布。2010年6月14日,OpenCL1.1发布,2011年11月19日,OpenCL1.2发布,2013年11月19日,OpenCL2.0分布。
OpenCL是一个异构并行计算平台编写程序的工作标准,此异构计算可映射到CPU、GPU、DSP和FPGA等计算设备。OpenCL提供了底层硬件结构的抽象模型,旨在提供一个通用的开发的API。开发人员可以编写在GPU上运行的通用计算程序,而无需将其算法映射到OpenGL或DirectX的3D图形的API上。
为了描述OpenCL设计的核心,Khronos Group将OpenCL异构并行计算架构划分为平台模型(platform model)、存储器模型(memory model)、执行模型(execution model)和编程模型(programming model)。这些模型相互独立、有相互联系,组成了OpenCL的有机整体。
平台模型是关于OpenCL如何看待硬件的一个抽象描述。OpenCL平台模型由主机以及相连的一个或多个OpenCL设备组成。如下图所示,通常主机是包含X86或ARM处理器的计算平台。OpenCL设计可以是CPU、GPU、DSP、FPGA或硬件商提供,OpenCL开发商支持的任何其他处理器。每个OpenCL设备有一个或者多个计算单元(Compute Units,CU),而每个计算单元又可以由一个或多个单元(Processing Elements,PE)组成,处理单元是设备上执行数据计算的最小单元。
OpenCL程序包含主机端程序和设备端内核(kernel)程序。主机端程序运行在主机处理器上,主机端程序以命令方式将内核程序从主机提交到OpenCL设备,OpenCL设备在处理单元上执行计算。
内核在OpenCL设备上执行,完成OpenCL应用的具体工作,内核通常是一些计算量大,逻辑比较简单的函数,OpenCL设备通过内核将输入数据就散处理后输出到主机。在OpenCL中定义了三类内核:
由于OpenCL设备通常没有I/O处理功能,因此I/O操作通常由主机承担,这意味着程序开始执行时,数据通常在主机上,故OpenCL设备需要从主机上获得数据,在OpenCL设备计算完成后,有需要将数据从OpenCL设备复制回主机。
对于OpenCL执行模型来说,最重要的就是上下文、命令列队和内核三个概念。
OpenCL程序的计算工作是OpenCL设备上执行的,不过主机在OpenCL应用中扮演着重要的角色。主机使用OpenCL API来创建和管理上下文,内核在此上下文中执行。包含了:
设备的选择取决了具体问题和运行的内核,主机可能选择CPU、一个GPU、CPU+GPU、多个GPU等多个组合方案。
OpenCL是一套跨平台计算框架,对OpenCL开发人员来说,你可能并不知道OpenCL应用最终会在CPU平台、GPU平台还是FPGA平台上运行的,只知道目标符合OpenCL规范。解决的方法就是主机程序根据上下文中设备特性,在运行时从代码中构建程序对象。为了在性能和平台无关之间均衡,OpenCL提供了两种方式从代码中构建对象,一种是从程序源代码中构建,另外一种是从源代码中已经编译好的代码上构建。
OpenCL没有定义主机代码如何工作的具体细节,知识定义了它通过命令队列与OpenCL设备如何交互。命令队列有主机或运行在设备中的内核(该功能需要支持OpenCL2.0的设备)提交给命令队列。命令会在命令队列中等待,直至别调度到OpenCL设备上执行。放入命令队列中的命令分为下列三种类型:
命令可以异步方式执行,在这种方式下主机或运行在设备中内核向命令队列提交命令,然后继续工作,而不必等待命令完成。如果有必要等待一个命令完成,可以利用命令执行相关的同步机制进行同步。
一个命令队列中命令执行时可以有以下两种模式:
OpenCL平台都支持按序模式,但乱序模式是可选的。
与内核相关的计算,在运行时只作为内核实例执行。常规方法上,可以通过主机程序多次启动内核实例来执行,但这样会显著地增加开销或加重应用程序控制流。一个方便有效的方法就是从内核内部嵌套内核命令队列。对支持OpenCL2.0的设备,可以在设备上入队内核,不需要主机程序参与,这称为设备端队列,实现了设备端队列,实现了嵌套并行。
主机发出一个命令,提交一个内核到OpenCL设备上执行,OpenCL运行时将会创建一个整数索引空间。索引空间是OpenCL支持的一个N维值的网格,称为NDRange,其中N为1,2,或3。三个长度为N的数据确定了NDRange的一下特征。
内核、关联内核参数值和定义索引空间的参数,这三个定义了一个内核实例。我们将执行内核的各个实例称为一个工作项(work-item),工作项将由它的索引空间中的坐标来标识,这个坐标就是工作组的全局ID,值从F到F加上该维度元素间1。每个工作项使用内核定义的同样的指令序列,尽管指令序列是相同的,但是由于代码中通过全局ID选择的数据不同,每个工作项的行为也不同。工作项提供了对索引空间细粒度的分解。
多个工作项组织成为工作组(work-group),工作组中工作项的数量由内核入队时的参数决定。工作组横跨了整个全局索引空间,提供了对索引空间粗粒度的分解。同样每个工作组被指定了一个唯一的ID,值从0开始,到该维度中工作组个数减1。除了一个全局ID,也赋予了一个局部ID来表示它所属工作组中的位置,这个局部ID的值从0快开始,到工作组内该维度元素个数减1。
例如:定义一个2维索引空间。索引空间的工作项大小为(AX,AY),每个工作组的大小为(BX,BY)、全局偏移ID(FX,FY)。索引空间的工作项总个数为AX×AY,每个工作组中工作项的个数为BX×BY,则工作组的大小为(CX,CY)的值为:
C X = c e i l ( A X / B X ) C Y = c e i l ( A Y / B Y ) CX=ceil(AX/BX)\\ CY=ceil(AY/BY) CX=ceil(AX/BX)CY=ceil(AY/BY)
工作项在工作组内的局部ID为(lx,ly),大小为(0,0)~(BX-1,BY-1)。工作组的ID为(Wx,Wy),大小为(0,0) ∼ \sim ∼(CX-1,CY-1)。对于工作项全局ID(gx,gy)可以结合全局ID(lx,ly)和工作组ID(Wx,WY)
g x = W x × B X + l x + F X g y = W y × B Y + l y + F Y gx=Wx\times BX+lx+FX\\ gy=Wy\times BY+ly+FY gx=Wx×BX+lx+FXgy=Wy×BY+ly+FY
OpenCL异构平台由主机端和设计端构成,存储器区域包括主机和设备的内存。
全局存储器和常量存储器可以在一个上下文内的一个或多个设备间共享,一个OpenCL设备关联局部存储器和私有存储器。
全局存储器中的数据内容通过存储器对象来表示,一个存储器对象就是对全局存储器区域的一个引用,主要有三种不同的类型:
主机和设备间有三种交互方式:读、写、映射和解映射以及拷贝。
通过映射,可以将设备全局存储器区域映射到主机可以访问的地址空间,而除了这种方式,在OpenCL2.0中,OpenCL通过共享虚拟存储器(Shared Virtual Memory,SVM)机制扩展了全局存储器区域到主机内存区域的方式。在OpenCL2.0中定义了三种SVM类型。
从GPU诞生之日,GPU的设计逻辑与CPU的设计逻辑相差很多。GPU从诞生之日起,定位是3D图像渲染设备。在设计GPU时从其功能出发,把更多的晶体管用于数据处理。使得GPU相比CPU有更强的单精度浮点运算能力。其中不得不提OpenGL(Open Graphics Library,开发图形库)。
OpenGL定义了一个跨编程语言、跨平台的应用程序接口规范,用于生成二维、三维图像。这个接口由近350个不同的函数调用组成,用来从简单的图像比绘制到复杂的三维图像。
当我们把绘制的图像传递给OpenGL后,OpenGL还要做很多才能完成3D空间到屏幕的投影。这一系列的过程称为OpenGL渲染流水线。渲染的过程如下:
在顶点装配和片段操作中,使用GPU中的着色器来进行相应操作。进行几何处理的处理器叫做顶点着色器,负责对顶点进行坐标转换、投影变换等。进行片段颜色处理叫做片段着色器。
从2007年以后,基于CUDA和OpenCL这些被设计为近似高阶语言的语法特性的新GPGPU原因,减低人们使用GPGPU的难度,平缓了开始时的学习曲线。
CUDA是集硬件与软件与一体的集成技术。CUDA C编程时在C99的扩展上进行的,但是只能在NVIDIA G80架构以后的GPU上。OpenGL编程模型设计时,借鉴和参考了CUDA的编程模型。从编程语言来看,OpenGL和CUDA语法基本类似。