OpenCL 2.0 规范 – Pipes(管道)
上一篇文章我们介绍了OpenCL2.0的新特性共享虚拟内存(SVM)。本文将继续讲述另一个新特性,“pipes(管道)”。
为了更好地理解下面内容,我们建议做好如下准备工作:
l 参考注释,通读每篇博文的代码。
l 请点击这里下载AMD OpenCL2.0驱动,下载页中列出了已支持平台的清单。
l 请点击这里下载范例代码
l 请尝试编写并运行自己的OpenCL2.0代码,可以进入OpenCL社区进讨论。
范例代码将运行于不同的AMD平台,例如Radeon HD8000系列。驱动页面会列出完整的可支持产品家族名单。
概述
OpenCL2.0引入了一新的工作机制用于在不同kernel程序间传递数据,其名字是“pipes(管道)”。一个pipe实质上是一个结构化缓冲区,里面含有“packets(包)”的集合空间—packet是指kernel程序类型对象。如其名字所示,这些数据包在pipe中是有序放置的。写入数据时pipe中会有一个写入结束端,读取数据时pipe中会有一个读取结束端。pipe可以说是buffer对象的附加,例如buffer和image。Pipes只能被kernel内程序函数访问而不能被主机访问。
特殊的内置函数read_pipe和write_pipe,实现了从kernel程序对pipes进行访问。一个kernel程序可以对pipe进行写入或读取,但不能同时进行。Pipes只在标准同步点时是一致的;多个kernel程序(甚至是硬件许可)对同一pipe的同时访问结果都是不确定的。主机端无法访问pipe。
创建pipes是非常简单的,在主机执行clCreatePipe就可以了。
你可以使用pipes实现各种功能。你可以在kernels程序间传递pipes。更好的是,可使用OpenCL2.0中的设备端任务队列(将在下一个博文中提及)特性来整合pipes,来动态构建不同的计算流程模式。
Pipes有两种类型:read(读取) pipe,可以读取不同的packets;另一个是write(写入) pipe,可以写入不同的packets。
注:不能对只读pipe进行写入,也不能对只写pipe进行读取。不能同时对pipe进行读取和写入。
访问Pipes的函数
OpenCL2.0新增了一个主机API函数来创建pipe。
- cl_mem clCreatePipe ( cl_context context, cl_mem_flags flags,
- cl_uint pipe_packet_size, cl_uint pipe_max_packets,
- const cl_pipe_properties * properties,
- cl_int *errcode_ret)
由该函数分配的内存可被作为只读或只写pipes传入kernels程序。Pipe对象只能被作为kernel程序参数或kernel程序函数进行传递,而不能在kernel中进行声明或作为程序全局的对象使用。
同时,在OpenCL2.0 spec中新增了一组pipes操作内建函数。重要的有:
l read_pipe (pipe p, gentype *ptr): 从pipe p读取packet到ptr
l write_pipe (pipe p, gentype *ptr):把由ptr指向的 packet写入pipe p
为了确保有足够的pipe结构空间来进行读取和写入,可以使用内建函数来预留足够的空间。例如,可以使用 reserve_read_pipe 或者 reserve_write_pipe来实现。这两个函数会返回一个预留ID,可用于进行实际操作。类似地,在workgroup级别也有相应的预留函数,work_group_reserve_read_pipe和work_group_reserve_write_pipe。使用commit_read_pipe 或commit_write_pipe可实现相应的提交操作(读取/写入)。
Pipes示例
该示例代码包含两个kernel程序:producer_kernel,对Pipe进行写入; consumer_kernel,对同一pipe进行读取。producer程序会写入一个随机数序列;consumer程序会读取该序列并生成一个柱状图。
主机负责创建pipe,用在上述两个函数中:
- rngPipe = clCreatePipe(context,
- CL_MEM_READ_WRITE,
- szPipePkt,
- szPipe,
- NULL,
- &status);
该代码创建了一个可以被kernel程序访问的pipe(读取/写入)。主机创建了两个kernel程序,producer_kernel和 consumer_kernel
。在producer程序中会为写入 pipe预留足够的空间:
- //reserve space in pipe for writing random numbers.
- reserve_id_t rid = work_group_reserve_write_pipe(rng_pipe, szgr);
下一步,kernel程序会执行以下函数进行pipe写入和提交:
- write_pipe(rng_pipe,rid,lid, &gfrn);
- work_group_commit_write_pipe(rng_pipe, rid);
类似地,consumer程序从该pipe进行读取:
- //reserve pipe for reading
- reserve_id_t rid = work_group_reserve_read_pipe(rng_pipe, szgr);
- if(is_valid_reserve_id(rid)) {
- //read random number from the pipe.
- read_pipe(rng_pipe,rid,lid, &rn);
- work_group_commit_read_pipe(rng_pipe, rid);
- }
之后consumer_kernel程序会使用该随机数集合来生成柱状图。CPU会生成一样的柱状图来检查正确性。其中,lid是一个work item的局部id,由get_local_id(0)来获取。
这个pipe示例展示了如何将pipe作为易用的数据结构来使用,使得两个kernel程序进行通信,其使用起来也是非常方便的。
在OpenCL1.2中,类似的通信行为需要主机的参与—尽管kernels程序可以不必把控制权交回主机来通信。然而使用Pipe后可以减少代码输入。此外,还有别pipes的示例与设备队列一起使用的,会在以后的文章中继续探讨。
总的来说,在OpenCL2.0中使用pipes会让代码变得更加简洁和易读。不要犹豫了,赶快编写代码来试试吧。
示例代码和自述文档
示例代码演示了OpenCL2.0的二分法搜索和区域增长特性。请根据以下链接进行访问:
1. 示例代码和自述文档请点击这里进行查阅
2. 请点击这里下载AMD OpenCL2.0驱动
3. 根据指引和自述文档来运行示例
欢迎进入OpenCL开发者论坛进行讨论和反馈