OpenCL2.0规范相对于1.2版本做出了重大改进,使得一个异构系统中各个硬件之间增强了通信与协作能力。在接下来的系列文章中,将着重讲解OpenCL2.0的新特性,探究其重要性以及对开发、性能等方面会产生什么影响。
实践出真知,为了更好地理解以下内容,我们建议做好下面的准备工作:
l 参考注释,通读每篇博文的代码。
l 请点击这里下载AMD OpenCL2.0驱动,下载页中列出了已支持平台的清单。
l 请点击这里下载范例代码
l 请尝试编写并运行自己的OpenCL2.0代码,可以进入OpenCL社区进讨论。
范例代码将运行于不同的AMD平台,例如Radeon HD8000系列。驱动页面会列出完整的支持产品家族名单。
共享虚拟内存SVM
概述
OpenCL2.0新增了两个重要特性:共享虚拟内存和设备端的任务队列。下面会先讲述共享虚拟内存。
先回想下在OpenCL1.2中是如何访问内存的。由于主机和OpenCL设备间没有共享相同的虚拟地址空间,因此必须各自对主机内存,设备内存进行管理,然后再就两者间的通信进行处理。一个典型的例子:OpenCL设备端不能使用主机端的内存指针。
OpenCL2.0突破了这个限制:主机和OpenCL设备可以共享相同范围的虚拟地址,因此可以不必再在设备间进行buffer复制。换句话说,无需始终查看buffer状态,并在设备间进行复制,直接使用共享指针就可以了。
OpenCL2.0定义了两种SVM机制,通过区分SVM buffer的共享粒度,分为粗粒度(coarse-grain)和细粒度(fine-grain)SVM。对粗、细粒度SVM的更新对其它设备来说在下述同步点时可见:
● 粗粒度(Coarse-grain)SVM:同步点包括:SVM内存的映射(mapping)和解除映射(unmapping),kernel程序启动和结束。因此,对粗粒度SVM的更新放生在Kernel程序调用结束,或SVM buffer被解除映射时。。粗粒度缓存有一个固定的虚拟地址区,被分配给所有设备使用。
● 细粒度(Fine-grain)SVM:同步点除了包括处理度SVM所定义的同步点外,还包括原子操作。因此,更新信息也会在下面两个原子操作级别中可见:
l SVM缓存
l SVM系统—系统内存的任何地方(在细粒度系统SVM中)
注意:细粒度SVM是OpenCL2.0的可选特性,目前的14.41驱动不支持细粒度SVM。我们将在今后提供对细粒度SVM的支持。
接下来会讲述如何使用粗粒度SVM。
使用粗粒度SVM内存
在OpenCL2.0中,通过调用clSVMAlloc来创建主机和OpenCL设备的共享SVM缓冲区。使用指向该buffer的指针以及所指向的数据结构,在异构编程系统中非常有用。例如以下一些的典型场合:
1. 主机透过clSVMAlloc来创建SVM缓冲区
2. 主机通过clEnqueueSVMMap来进行SVM缓冲区与指针的映射
3. 主机把数据结构,指针写入或更新SVM缓冲区
4. 主机使用clEnqueueSVMUnmap来解除SVM缓冲区映射
5. 主机调用clSetKernelArgSVMPointer和/或clSetKernelExecInfo来把SVM缓冲区作为参数传送至kernel程序
6. OpenCL2.0设备处理SVM缓冲区中的结构,包括及后的/更新的指针
7. 如有需要,重复步骤2到6
让我们来思考一个在二分法搜索树中进行并行搜索的例子。(SVMBinarySearchTree示例代码可透过文末的链接进行下载。)
该示例在host程序上使用粗粒度SVM创建了二分法搜索树的根节点:
svmTreeBuf = clSVMAlloc(context, CL_MEM_READ_WRITE, numNodes*sizeof(node), 0);
svmSearchBuf = clSVMAlloc(context, CL_MEM_READ_WRITE, numKeys*sizeof(searchKey), 0);
host
程序创建了两个缓冲区
svmTreeBuf
和
svmSearchBuf
,分别用来存放指定的树和搜索
keys
值。填充该树后,这两个缓冲区会作为参数传入
kernel
程序中。
下一步是创建树和使用clSVMEnqueueMap以及clSVMEnqueueUnmap来填充svmTreeBuf。CPU端函数cpuCreateBinaryTree说明了这个机制;这里要注意相关映射/解除映射API的使用。
然后host程序创建了在svmSearchBuf中用于搜索的keys值,如cpuInitSearchKeys方法所展示的。接下来,host程序使kernel程序入队,然后根据给出的keys值在svmSearchBuf中进行二分法树搜索,同时使用clSetKernelArgSVMPointer为kernel程序参数进行赋值:
int status = clSetKernelArgSVMPointer(sample_kernel, 0, (void *)(svmTreeBuf));
status = clSetKernelArgSVMPointer(sample_kernel, 1, (void *)(svmSearchBuf));
这里要注意的是程序会把svmTreeBuf以及svmSearchBuf作为参数传入kernel程序。下面的node结构体演示了如何在host程序中使用指针来创建左节点和右节点:
typedef struct nodeStruct
{
int value;
struct nodeStruct* left;
struct nodeStruct* right;
} node;
从这里开始SVM的优点得到显现。由于主结构及其子节点都属于SVM内存,所有这些节点的指针值在GPUs中也是有效的。
以下代码显示了OpenCL2.0设备上的kernel程序是如何直接搜索树的:
while(NULL != searchNode)
{
if(currKey->key == searchNode->value)
{
/* rejoice on finding key */
currKey->oclNode = searchNode;
searchNode = NULL;
}
else if(currKey->key < searchNode->value)
{
/* move left */
searchNode = searchNode->left;
}
else
{
/* move right */
searchNode = searchNode->right;
}
}
每个work item会并行地在svmSearchKeys中搜索元素,然后为节点在searchKey结构中对oclNode进行赋值。
对树的更新发生在主机(CPU)或GPU中,但不会同时进行。
由于树是在host程序中生成的,同时OpenCL1.2不支持SVM,刚才的示例很难在OpenCL1.2中实现。在OpenCL1.2中,必须以数组形式存储树,然后把数组复制到GPU内存(指定合适的偏移量),而后再把数组复制回主机。
总的来说,2.0版本在性能和易用性方面都超越了上个版本,如下面表格所示。
OpenCL1.2中GPU运算时要耗费时间来复制和处理缓冲区,而在OpenCL2.0中GPUs只需单独地处理kernels程序。
注:上述数据来自的硬件配置是32GB RAM的Kaveri APU,操作系统是Windows 8.1。单位是毫秒(ms)。
如上表所示,OpenCL1.2所在列的运算时间包括了GPU运行时间,从主机到设备的缓冲区传递时间,把缓冲区传送至数组及偏移的时间,从设备传送回主机的时间。
综上所述,SVM简化了编程工作,提高了效率。不相信?赶快编写你的OpenCL2.0程序来一探究竟吧。
示例代码和自述文档
示例代码演示了如何使用OpenCL2.0的粗粒度SVM来执行二分法树搜索算法。
1. 示例代码和自述文档请点击这里进行查阅
2. 请点击这里下载AMD OpenCL2.0驱动
3. 根据指引和自述文档来运行示例
4. 欢迎进入OpenCL开发者论坛进行讨论和反馈