经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C 语言 » 查看文章
OpenCL学习(1)
来源:cnblogs  作者:jzhoucdc  时间:2021/2/18 15:36:28  对本文有异议

 阅读<<OpenCL实战>>和<<OpenCL异构并行编程实战>>的学习记录. 

1.获取平台信息

OpenCL通过cl_platform_id来区分不同的平台,比如你电脑安装的是AMD的显卡,并且装了相应的SDK这便是一个平台,可以检测到相应的cl_platform_id结构。创建平台结构可以分为两步:

一是为cl_platform_id结构分配内存空间.

二是调用这个函数

clGetPlatformIDs(cl_uint num_entries, cl_platform_id *platforms,  cl_uint *num_platforms)

初始化这些数据结构。(cl_platform_id结构)

num_entries表示你想要检测平台的上限数会放进platforms数组中表示上限如果为0函数会报错,platforms表示的是用于存放cl_platform_id的地址,num_platforms表示在程序运行时能够检测平台的数量。

clGetPlatformIDs()这个函数将返回一个整数,0表示成功,负数表示失败。

    cl_platform_id* platforms; //指向存放cl_platform_id的内存
    cl_uint num_platforms; //实际平台数
    clGetPlatformIDs(5, NULL, &num_platforms); //程序运行时函数获取实际平台数
    platforms = (cl_platform_id*)malloc(sizeof(cl_platform_id) * num_platforms); //分配内存空间
    clGetPlatformIDs(num_platforms, platforms, NULL); //初始化结构

 接着我们获取平台信息,clGetPlatformIDs函数为我们创建了由cl_platform_id组成的数组,但没有提供关于平台本身的信息,所以我们要通过

cl_uint clGetplatformInfo(cl_platform_id platform, cl_platform_info param_name, size_t param_value_size, void *param_value, size_t *param_value_size_ret)

第二个参数cl_platform_info param_name是表示所需要的信息类型,取值范围如下

  • CL_PLATFORM_NAME
  • CL_PLATFORM_VENDOR
  • CL_PLATFORM_VERSION
  • CL_PLATFORM_PROFILE
  • CL_PLATFORM_EXTENSIONS 

函数通过char型数组返回需要的信息。

数组的长度(字节)由最后一个参数size_t *param_value_size_ret来确定。

第三个参数size_t param_value_size用于告诉函数要保存的字节数.

函数的使用有以下两种使用方式

实例(1):

 char pform_name[40];//用于装返回的查询信息
 clGetPlatformInfo(platforms[0], CL_PLATFORM_NAME, sizeof(pform_name), &pform_name, NULL);

这段代码是先为char型数组分配内存,然后调用函数clGetPlatformInfo()

实例(2):

size_t size_num;
char* ext_char;
clGetPlatformInfo(platforms[0], CL_PLATFORM_EXTENSIONS, NULL, NULL, &size_num);    
    
ext_char = (char*)malloc(sizeof(char) * size_num);
clGetPlatformInfo(platforms[0], CL_PLATFORM_EXTENSIONS, sizeof(ext_char), ext_char, NULL);

printf("CL_PLATFORM_EXTENSIONS: %s\n", ext_char);

这段代码是先调用函数确定查询信息具体的字节数,然后为信息的存储分配内存.

2.查询设备

OpenCL编程中用cl_device_id来表示某个设备,通过这个clGetDeviceIDs函数来创建设备结构

cl_int clGetDeviceIDs(cl_platform_id platform, cl_device_type device_type, cl_uint num_entries, cl_device_id *devices, cl_uint *num_devices)

第一个参数cl_platform_id platform是对应平台选择.

第二个参数cl_device_type device_type是选择OpenCL的设备类型:

  • CL_DEVICE_TYPE_ALL
  • CL_DEVICE_TYPE_DEFAULT
  • CL_DEVICE_TYPE_CPU
  • CL_DEVICE_TYPE_GPU
  • CL_DEVICE_TYPE_ACCELERATOR

第三个参数cl_uint num_entries限制了数组存放设备的数量.

访问设备的函数为:

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)

 通过这个函数将你需要查询的信息保存到param_value指向的内存里面。

第二个参数cl_device_info param_name(具体设备信息)取值超过50种,书中只选了7种

  • CL_DEVICE_NAME    char[]
  • CL_DEVICE_VENDOR    char[]
  • CL_DEVICE_EXTENSIONS    char[]
  • CL_DEVICE_GLOBAL_MEM_SIZE    cl_ulong
  • CL_DEVICE_ADDRESS_BITS    cl_uint
  • CL_DEVICE_AVAILABLE    cl_bool
  • CL_DEVICE_COMPILER_AVAILABLE     cl_bool

例子:

(查看GPU和设备地址空间大小以及设备所支持的OpenCL设备)

    cl_platform_id platform;
    cl_device_id dev;
    cl_uint addr_data;
    cl_int err;
    char name_data[48], ext_data[4096];

    err = clGetPlatformIDs(1, &platform, NULL);
    if (err < 0) {
        perror("Couldn't find any platforms");
        exit(1);
    }
err
= clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &dev, NULL); if (err == CL_DEVICE_NOT_FOUND) { err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_CPU, 1, &dev, NULL); } if (err < 0) { perror("Couldn't access any devices"); exit(1); } err = clGetDeviceInfo(dev, CL_DEVICE_NAME, 48 * sizeof(char), name_data, NULL); if (err < 0) { perror("Couldn't read extension data"); exit(1); } clGetDeviceInfo(dev, CL_DEVICE_ADDRESS_BITS, sizeof(addr_data), &addr_data, NULL); clGetDeviceInfo(dev, CL_DEVICE_EXTENSIONS, 4096 * sizeof(char), ext_data, NULL); printf("NAME: %s\nADDRESS_WIDTH: %u\nEXTENSIONS: %s\n", name_data, addr_data, ext_data);

3.查询OpenCL上下文(通过上下文管理设备)

OpenCL中上下文是由cl_context来表示,可以通过下面两个函数创建

1. cl_context clCreateContext(const cl_conntext_properties *properties, cl_uint num_devices, const cl_device_id *devices, (void CL_CALLBACK *notify_func)(...), void *user_data, cl_int *error)

2. cl_context clCreateContextFromType(const cl_conntext_properties *properties, cl_device_type device_type, (void CL_CALLBACK *notify_func)(...), void *user_data, cl_int *error)

clCreateContext()是通过直接确定给定的设备来完成创建的过程,

而clCreateContextFromType()是通过给设备的类型方式来完成创建的过程。cl_device_type device_type参数的选择如下图

 

const cl_conntext_properties *properties指针是指向由一个属性名和属性值组成的数组并且数组以0元素结尾。

void *user_data指针是提供报错信息。还有两个函数都可以提供回调函数作为参数,当上下文运行错误时,相应的回调函数被调用。参数cl_int *error是一个表示函数错误代码的整型变量,如果cl_context成功创建,他的值为0.

接着获取上下文信息

 clGetContextInfo(cl_context context, cl_context_info param_name, size_t param_value_size, void *param_value, size_t *param_value_size_ret)

cl_context_info param_name参数的值必须是cl_context_info中的一个,如下所示

4.查询OpenCL程序(将设备保存在程序中)

 在OpenCL中内核是在程序中声明的函数(代码中使用_Kernel限定符声明的函数),OpenCL中的程序由一组内核组成(一个程序由cl_program表示).

为了运行OpenCL内核,需要一个程序(源const siz文件或者二进制文件了)

创建程序

1.代码为文本形式

cl_program clCreateProgramWithSource(cl_context context, cl_uint count, const char** strings, const size_t* lengths, cl_int* errcode_ret)

如果是通过多个文件创建程序, 每个文件的内容都需要都需要报错一个由字符串组成的指针数组(char**)之中, count表示所需文本的个数, lengths为每个文本字符串的大小。

下面程序例子讲展示如何通过单文件创建cl_progame,分为三步,一确定.cl文件, 二是讲文件内容读到缓存之中, 三是通过缓存创建cl_program

例:

/*确定源文件数量*/
program_handle = fopen(“kernel.cl”, "r");
fseek(program_handle, 0, SEEK_END);
program_size = ftell(program_handle);
rewind(program_handle);
/*将文件内容读取到缓存*/
program_buffer = (char*)malloc(program_size+1);
program_buffer[program_size] = '\0';
fread(program_buffer, sizeof(char), program_size, program_handle);
fclose(program_handle);
/*通过缓存创建程序*/
program = clCreateProgramWithSource(context, 1, (const char**)program_buffer, program_size, &err);

2.通过二进制文件创建

cl_program clCreateProgramWithBinary(cl_context, cl_uint num_devices, const cl_device_id* device_list, const size_t * lengths, const unsigned char** binaries, cl_int* binary_status, cl_int* errcode_ret)

编译程序(cl_program program, cl_uint num_devices, const cl_device_id *devices, const char *options, (void CL_CALLBACK *notify_func) (...), void *user_data)

const char *options是编译器的编译选项:

 下面代码展示如何使用这些编译选项,

const char options[] = "-cl-std=CL1.1 -cl-mad-enable -Werror";
clBuildProgram(program, 1, &device, options, NULL, NULL);

获取程序信息

当创建和编译完程序,你就可以调用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)

 clGetProgramInfo()函数是获取和程序相关的数据结构的信息,比如上下文和目标和设备.

cl_program_info param_name是所需要数据的信息类型

 clGetProgramBuildInfo(cl_program program, cl_device_id device, cl_program_build_info param_name, void *param_value, size_t *param_value_size_ret)

clGetProgramBuildInfo()函数提供的是程序的编译信息, cl_program_build_info param_name参数的选择如下

 如果clBuilProgram的返回值小于0说明程序的构建过程可能已经失败

5.创建OpenCL内核(将函数打包为内核)

当第四步程序编译和链接结束后,可以将函数打包成名为内核的数据结构, 内核可以被发送到命令队列然后发给设备。

每个内核都可以用cl_kernel的结构来表示,目前暂时不讨论如何配置内核参数

创建内核

OpenCL中定义了两个函数来通过cl_program来创建cl_kernel结构。

1. clCreateKernelsInProgram(cl_program program,   cl_uint num_kernels,   cl_kernel *kernels,   cl_uint *num_kernels_ret)

cl_uint *num_kernels_ret保存的是可以使用的内核数量,和之前一样两次调用这个函数可以确定需要分配的内存大小。 而新的cl_kernels结构会存放到kernels数组中

如果你更愿意创建单个内核

2. clCreateKernel(cl_program program,   const char *kernel_name,   cl_int *error)

这个函数只会返回一个cl_kernel结构, 如果你要创建多个内核,只有反复调用这个函数。

//使用例子
char kernel_name[] = "convolve";
kernel = clCreateKernel(program, kernel_name, &error);

 

这段代码clCreateKernel函数会检查是否有个叫convolve的函数, 如果不存在,函数将返回NULL, error的值为CL_INVALID_KERNEL_NAME

获取内核信息

当创建完cl_kernel后, 可以获取相关信息。

clGetKernelInfo(cl_kernel kernel,
cl_kernel_info param_name,
size_t param_value_size,
void * param_value,
size_t * param_value_size_ret) 

  cl_kernel_info param_name参数

6.创建命令队列(用命令队列保存内核)

创建命令队列

在OpenCL中命令队列用cl_command_queue表示。

cl_command_queue clCreateCommandQueue(cl_context context,
cl_device_id device,
cl_command_queue_properties properties,
cl_int * errcode_ret)

第三个参数cl_command_queue_properties properties,在以下两个中许一个

  • CL_QUEUE_PROFILING_ENABLE ---- 性能分析事件
  • CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE ---- 能命令队列乱序执行

入列内核执行命令

OpenCL中提供了很多以clEnqueue开头的函数,他们都是通过命令队列向设备发送命令, 这里介绍clEnqueueTask()函数,他是通过命令队列向设备发送内核执行命令

clEnqueueTask(cl_command_queue command_queue,
cl_kernel kernel,
cl_uint num_events_in_wait_list,
const cl_event * event_wait_list,
cl_event * event)

第一个cl_command_queue command_queue表示向某个设备发送命令, 第二个cl_kernel kernel包含所需的内核函数

这个函数执行完, 一条内核执行命令将会被发送到命令队列之中, 设备将会在处理命令时,执行内核函数

7.总结

通过第一步到第6步, 我们成功向设备发送命令。 我们首先创建了一个或者多个cl_platfrom_id, 然后利用这些平台, 然后找到相关的设备(cl_device_id表示),可以通过clGetDeviceInfo函数来找到设备的相关信息,一旦确定设备我们可以将其关联在上下文中(cl_context).

接着我们会读入内核函数,并创建程序(cl_program), 用clBuildProgram函数来构建整个程序,一旦程序构建成功,主机程序会为包含的内核函数创建cl_kernel结构

为达成主机与设备的通信, 主机创建cl_command_queue结构,将各个命令发送到队列中,每条命令通知对应的目标设备完成相应的操作。

原文链接:http://www.cnblogs.com/jzhoucdc/p/14346418.html

 友情链接: NPS