OpenCL编程指南
1. 简介
OpenCL(Open Computing Language,开放计算语言)是一种开放的、跨平台的并行计算框架,由Khronos Group维护。它为开发者提供了统一的编程接口,使得应用程序可以在不同的硬件平台(CPU、GPU、DSP和其他处理器)上运行,从而提高了代码的可移植性和性能。OpenCL主要包含以下两个部分:
-
用于编写 kernels(在OpenCL设备上运行的函数) 的语言(基于C99)
-
用于定义并控制平台的API
如下图所示,OpenCL框架包含两个API:平台层(Platform Layer)API 和 运行时(Runtime)API。
-
平台层API在主机(Host)CPU上运行,主要用于查询和使能系统中可用的并行处理器或计算设备。通过查询可用的计算设备,应用程序可以移植到不同系统中运行,从而适应各种硬件加速设备的组合。
-
运行时API则使应用程序能够为其选定的计算设备编译内核程序,并将其并行加载到这些处理器上执行。内核程序执行完成后,运行时API还将用于收集和处理结果。
2. OpenCL程序的执行
OpenCL将内核程序视为可执行代码的基本单元(类似于C函数)。内核能够以数据并行或任务并行的方式执行。一个 OpenCL 程序是由多个内核和函数组成的集合(类似于具有运行时链接的动态库)。
OpenCL 命令队列由主机应用程序用于将内核和数据传输函数发送到设备以执行。通过将命令排队到命令队列中,内核和数据传输函数可以异步且并行地与主机应用程序代码一起执行。
命令队列中的内核和函数可以按顺序或乱序执行。一个计算设备可以拥有多个命令队列。
下图展示了执行OpenCL Kernel的流程:
执行 OpenCL 程序的完整步骤如下:
-
查询可用的 OpenCL 平台和设备
-
为一个或多个平台中的 OpenCL 设备创建上下文
-
为上下文中的 OpenCL 设备创建并构建程序
-
从程序中选择要执行的内核
-
为内核创建内存对象以进行操作
-
创建命令队列以在 OpenCL 设备上执行命令
-
获取执行结果并清理环境
更详细的介绍可以参考:
3. 主要API
3.1 OpenCL平台
选择OpenCL平台是OpenCL的第一步,clGetPlatformIDs()
这个API就是查找制定系统上的可用OpenCL平台的集合。
cl_int clGetPlatformIDs(cl_uint num_entries, cl_platform_id *platforms, cl_uint *num_platforms)
-
num_entries:表示OpenCL平台的索引值。设置为0,且platforms为NULL时用于查询可用的平台数
-
platforms:表示平台的指针
-
num_platforms:表示OpenCL平台的数量,一般作为返回值
这个API一般会调用两次,用来查询和获取到对应的平台信息,使用方式如下:
cl_int err = 0; // 错误代码
cl_uint num_platform = 0; // 平台数量
cl_platform_id *platform = NULL; // 平台 ID 指针
err = clGetPlatformIDs(0, NULL, &num_platform); // 获取平台数量,第一个参数为要获取的平台数量,第二个参数为平台 ID 数组,第三个参数为返回的平台数量
if (err!= CL_SUCCESS) { // 检查错误
fprintf(stderr, "Failed to create context: %d\n", err); // 输出错误信息
exit(-1); // 退出程序
}
platform = (cl_platform_id*)malloc(sizeof(cl_platform_id) * num_platform); // 分配内存以存储平台 ID
err = clGetPlatformIds(num_platform, platform, NULL); // 获取平台 ID
3.2 OpenCL设备
当平台确定好之后,下一步就是查询平台上可用的设备:
/**
* 获取设备 ID 的函数
* @return cl_int 错误代码
*/
cl_int clGetDeviceIDs(
cl_platform_id platform, //平台 ID
cl_device_type device_type, //设备类型
cl_uint num_entries, //要获取的设备 ID 数量
cl_device_id *devices, //存储设备 ID 的数组
cl_uint *num_devices //实际获取到的设备 ID 数量
);
// 使用:
cl_int err = 0; // 用于存储错误代码
cl_uint num_devices = 0; // 用于存储设备数量
cl_device_id *devices = NULL; // 用于存储设备 ID 的指针
err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 0, NULL, &num_devices); // 获取 GPU 设备数量,platform 是平台,CL_DEVICE_TYPE_GPU 表示获取 GPU 设备,0 表示不指定特定设备,NULL 表示不返回设备 ID 列表,&num_devices 用于存储设备数量
if (err!= CL_SUCCESS) // 检查是否有错误
exit(-1); // 如果有错误,退出程序
devices = (cl_device_id*)malloc(sizeof(cl_device_id) * num_devices); // 为设备 ID 分配内存
err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, num_devices, devices, NULL); // 获取设备 ID 列表
cl_device_type
参数描述如下:
-
CL_DEVICE_TYPE_CPU:将 CPU 作为 OpenCL 设备
-
CL_DEVICE_TYPE_GPU:GPU 设备
-
CL_DEVICE_TYPE_ACCELERATOR:FPGA 设备属于加速卡类型的 OpenCL 设备,加速卡设备
-
CL_DEVICE_TYPE_DEFAULT:与平台关联的默认 OpenCL 设备
-
CL_DEVICE_TYPE_ALL:平台支持的所有 OpenCL 设备
3.3 OpenCL上下文
OpenCL中上下文为了内核的正确执行,进行协调和内存管理。上下文对象可以通过clCreateContext()
进行创建。
// 创建 OpenCL 上下文
cl_context clCreateContext(
const cl_context_properties *properties, // 上下文属性列表
cl_uint num_devices, // 设备数量
const cl_device_id *devices, // 设备 ID 数组
void (CL_CALL_BACK *pfn_notify)(const char *errinfo, const void *private_info, size_t cb, void *user_data), // 与user_data共同做一个错误通知回调函数,报告上下文生命周期中出现的错误信息
void *user_data, // 用户提供的数据,将传递给错误通知回调函数
cl_int *errcode_ret // 用于返回错误代码的指针
);
OpenCL提供了另一个API也能用来创建上下文:通过clCreateContextFromType()
可以使用所有的设备类型(CPU、GPU和ALL)创建上下文。