项目需要接触到opencl语言,日前购买了参考书《OpenCL实战》(Matthew Scarpino著 陈睿译),之后会详细阅读该书并对关键知识做出整理。
OpenCL编程基础
OpenCL简介
目前,GPU运算蓬勃发展,工程师和科研人员都得出了统一的结论,CPU/GPU系统昭示着未来超级计算发展的方向。
语言之间的对比
- 传统C或C++编译目标为CPU
- CUDA只能针对Nvidia的GPU,而非CPU
- OpenCL不仅可以在AMD、Nvidia、Intel的CPU或GPU上运行,甚至可以在Sony的PS3上运行
- OpenCL支持quartus的高层次综合,可以被编译器转化为硬件描述语言(Verilog,VHDL),这也是我阅读此书的目的
本书的目标为展示如何编写跨平台架构的应用程序,以及最大可能利用硬件资源。
OpenCL并不是一门独立的语言,而是建立在C和C++基础上的一套标准,扩展定义了一些数据类型,数据结构以及函数。开发人员已经针对Java和Python设计了一系列的接口库,但标准中只支持C和C++编写的API。
OpenCL的三个特点
- 可移植性——可以在多种设备上运行,切换设备只需要重新编译
- 标准化的向量处理——可处理向量运算
- 并行编程——多个处理单元同时执行任务
在OpenCL中,不同的运算任务被称为内核(kernel),主机可以选择将内核程序发送到不同的设备中执行。主机应用程序通过上下文来管理所连接的设备,主机从一个名为程序(program)的内核容器中选择函数,才能创建出内核程序。
主机编程:基本的数据结构
OpenCL的数据类型同C/C++类似。
获取平台信息(Platform)
OpenCL开发者并不需要深入了解底层设备信息,只需通过函数cl_platform_id即可应对
cl_int clGetPlatformIDs(cl_unit num_entries, cl_platform_id *platforms, cl_unit *num_platforms)
函数的实际作用为将cl_platform_id放入platforms指向的内存空间中,将可用平台数作为num_platforms保存。函数返回一个整数值,当返回0时表示平台检测成功。
上述函数并不能获取平台信息,但clGetPlatformInfo可以。
cl_int clGetPlatformInfo(cl_platform_id platform,
cl_platform_info param_name, size_t param_value_size,
void *param_value, size_t *param_value_size_ret)
变量param_name为枚举类型,其中包含了我们需要的各种信息。
访问安装设备(device)
在向设备发送内核之前,我们需要创建一个cl_device_id结构来表示某个设备。clGetDeviceIDs会将OpenCL设备所对应的结构保存在cl_device_id中。
cl_int clGetDeviceIDs(cl_platform_id platform,
cl_device_type device_type, cl_unit num_entries,
cl_device_id *devices, cl_unit *num_devices)
第一个参数为感兴趣的平台,第二个参数表示的是设备类型,取值如下所示。
同样的,可以使用clGetDeviceInfo来获取设备信息。
cl_int clGetDeviceInfo(cl_device_id device,
cl_device_info param_name, size_t param_value_size,
void *param_value, size_t *param_value_size_ret)
下图为cl_device_info返回的中多次参数中的七个。
通过上下文管理设备(context)
OpenCL中,上下文标记了一部分设备,而非平台上的所有设备,只有选中的设备可以一起工作。上下文是命令队列创建的基础,命令队列是主机和设备间通信的纽带。
可以通过下述两个函数创建上下文。
cl_context clCreateContext(const cl_context_properties *properties,
cl_unit num_devices, const cl_device_id *devices,
(void CL_CALLBACK *notify_func)(...),
void *user_data, cl_int *error)
cl_context clCreateContextFromType(
const cl_context_properties *properties,
cl_device_type device_type,
(void CL_CALLBACK *notify_func)(...),
void *user_data, cl_int *error)
)
两个函数的区别为,前者直接确定设备,后者只给定了设备类型,使用后者的好处为创建上下文的过程中并不需要访问平台或设备。
获取上下文信息与之前函数十分类似。
cl_int clGetContextInfo(cl_context context,
cl_context_info param_name, size_t param_value_size,
void *param_value, size_t *param_value_size_ret)
param_name必须设定为cl_context_info枚举类型中的一个。下表为其可选取值:
将设备代码保存在程序中(program)
内核和程序都保存可执行代码,但内核表示的只是设备上执行的某个函数,而程序则是由一对内核所构成的。在OpenCL中,一般程序由cl_program构成。可以通过函数clCreateProgramWithSource和函数clCreateProgramWithBinary创建程序。创建程序前,需要将内核代码以文本格式保存在缓存数组中。
程序编译函数为:
clBuildProgram(cl_program program, cl_uint num_devices,
const cl_device_id *devices, const char *options,
(void CL_CALLBACK *notify_func)(...), void *user_data)
第四个选项为编译器的编译选项,程序编译选项如下图所示:
可以通过调用函数clGetProgramInfo和函数clGetProgramBuildInfo来访问相关信息。第一个函数提供了和程序相关的数据结构的信息,例如程序的上下文和目标设备。第二个函数提供的是程序的编译信息。
clGetProgramInfo(cl_program program, cl_program_info param_name,
size_t param_value_size, void *param_value,
size_t *param_value_size_ret)
另外,函数clGetProgramBuildInfo十分重要,他是了解程序构建过程的唯一方法。
clGetProgramBuildInfo(cl_program program, cl_device_id device,
cl_program_build_info param_name,
size_t param_value_size, void *param_value,
size_t *param_value_size_ret)
cl_program_build_info也是一个枚举类型,其中包含了程序构建的信息。
将函数打包为内核(kernel)
在程序编译和链接结束之后,需要将函数打包为名为内核的数据结构,内核是可以部署的,并能发送到命令队列中,再发送到设备上,内核结构用cl_kernel来表示。
OpenCL可以用cl_program结构来创建cl_kernel结构。函数签名如下
clCreateKernelsInProgram(cl_program program, cl_unit num_kernels,
cl_kernel *kernels, cl_unit *num_kernels_ret)
获取内核信息函数如下:
clGetKernelInfo(cl_kernel kernel, cl_kernel_info param_name,
size_t param_value_size, void *param_value,
size_t *param_value_size_ret)
用命令队列保存内核(queue)
主机和设备间通过命令队列来通信,命令队列为内核执行的顺序。