OpenCL入门(三)——Hello Word

转自:https://blog.csdn.net/u011028771/article/details/52588433?locationNum=10&fps=1

这是OpenCL的第一个程序,所谓的helloword!本程序实现的是向量加法。首先从主要的主机API开始介绍。

1.创建平台结构

cl_int clGetPlatformIDs(cl_int num_entries,
                   cl_platform_id *platforms,cl_uint *num_platforms);
  • 1
  • 2
  • 3

参数说明: 
num_entries:用户设定需要检测到的平台的上限,即可以放到platforms中的平台上限。既然是上限当然不可以设置为0. 
platforms:存放检测到的平台; 
num_platforms:实际检测到的平台数量;注意与num_entries区分。 
在使用OpenCL时经常会用到一种“函数调用——内存分配——函数调用”的模式。下面以一个简单的例子来描述num_entries与num_platforms的区别。

//调用函数,获得num_platforms
error = clGetPlatformIDs(1, NULL, &num_platforms);
    if (error != 0) {
        printf("Check palrforms failed!");
        return -1;
    }
//分配内存空间
    platforms_buffer = (cl_platform_id*)malloc(sizeof(cl_platform_id)*num_platforms);
//调用函数,将平台存入platforms_buffer
error = clGetPlatformIDs(num_platforms, platforms_buffer,NULL);
    if (error != 0) {
        printf("Get platforms failed!");
        return -1;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2.创建设备结构

cl_int clGetDeviceIDs(cl_platform_id platform,
    cl_device_type device_type, 
    cl_uint num_entries,cl_device_id *devices, 
    cl_uint *num_devices); 
  • 1
  • 2
  • 3
  • 4
  • 5

该函数与创建平台结构的函数参数相同,增加了cl_platform_id参数和cl_device_type。这两个参数很容易理解,cl_device_type为枚举类型,任何一本OpenCl相关书籍都可查到其取值,不做赘述。cl_platform_id便是上一函数获得的平台ID。

error = clGetDeviceIDs(platforms_buffer[i], CL_DEVICE_TYPE_ALL, 
1, NULL, &num_devices);
if (platforms == NULL) {
    printf("Get GPU device failed!");
    return -1;
}
devices = (cl_device_id*)malloc(sizeof(cl_device_id)*num_devices);
error = clGetDeviceIDs(platforms, CL_DEVICE_TYPE_GPU, 
1, &devices, NULL);
if (error != 0) {
    printf("Get device failed!");
    return -1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

3.创建上下文

上下文是命令队列创建的基础,而命令队列则是设备与主机之间通信的纽带,所以上下文在主机程序中扮演着重要角色。 
主机可以通过多个上下文来管理设备,甚至一个平台可以创建多个上下文,但是每个上下文中的设备都必须来自同一个平台,不可以交叉。 
创建上下文的函数有两个,这里介绍一个。

cl_context clCreateContext(const cl_context_properties *properties,
                    cl_uint num_devices,const cl_device_id *devices,
                               (void CL_CALLBACK *notify_func)(...),
                               void *user_data,cl_int *error);
  • 1
  • 2
  • 3
  • 4
  • 5

各个参数解释以后会继续更新,本文可以在代码例子中看到简单用法。

4.创建程序

创建程序的函数同样也有两个,本文介绍其中一个。

clCreateProgramWithSource(cl_context context,cl_uint src_num,
       const char**src_string,const size_t *src_size,cl_int *err_code);
  • 1
  • 2
  • 3

该函数是通过文本来创建程序,每个文件中的内容都需要保存到一个由字符串组成的指针数组(char**)之中。参数src_num表示函数所需的文本个数,而参数src_sizes则表示每个文本字符串的大小。 
注意: 
该函数需要用到之前博客中写到的C语言文件操作函数fopen();fread()等,但是需要注意,之前提到默认为二进制,但是在OpenCL工程中如果用的是VS,最好加上b。“rb” 
或者使用open和read函数。

5.编译程序

cl_BuildProgram(cl_program program,cl_uint num_devices,
                const cl_device_id *devices,const char *options,                 
                (void CL_CALLBACK *notify_func)(...),void *user_data);
  • 1
  • 2
  • 3
  • 4

本部分介绍一个获取程序信息的函数:

cl_GetProgramBuildInfo(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);
  • 1
  • 2
  • 3
  • 4

该函数只要用于获得编译日志,了解kernel函数编译的错误信息。

error = clBuildProgram(program,1,&devices,NULL,NULL,NULL);
if (error < 0) {
    //确定日志文件的大小 
    clGetProgramBuildInfo(program,devices,CL_PROGRAM_BUILD_LOG,0,
                           NULL,&log_size);
    program_log = (char *)malloc(log_size+1);
    program_log[log_size] = '\0';
    //读取日志
    clGetProgramBuildInfo(program, devices, 
                           CL_PROGRAM_BUILD_LOG,
                           log_size+1, program_log, NULL);
    printf("%s\n",program_log);
    free(program_log);
    return -1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

6.创建内核

创建内核也有两个函数,本部分介绍一个。

clCreateKernel(cl_program program,const char *kernel_name,
                cl_int *error);
  • 1
  • 2
  • 3

7.创建命令队列

clCreateCommandQueue(cl_context context,cl_device_id device, 
                       cl_command_queue_properties properties,
                       cl_int *err);
  • 1
  • 2
  • 3
  • 4

枚举类型cl_command_queue_properties在两个值之间选择

 - CL_QUEUE_PROFILING_ENABLE——使能性能分析事件
 - CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE——使能命令队列的乱序执行。
  • 1
  • 2
  • 3

8.创建缓存对象

clCreateBuffer(cl_context context,cl_mem_flags options,
                size_t size,void *host_ptr,cl_int *error);
  • 1
  • 2
  • 3

该函数返回的是cl_mem,其中的有效载荷是参数host_ptr指针所指向的数据。二参数option用来配置缓存对象的个个性质。 
在此对三个主机内存性质进行区分:

  • CL_MEM_USE_HOST_PTR 内存对象将访问主机所指向的内存区域
  • CL_MEM_COPY_HOST_PTR内存对象将设为主机指针所指向的内存区域
  • CL_MEM_ALLOC_HOST_PTR分配主机可访问的内存区域,用于数据传输 
    主机与设备之间的数据交换分为:

  • 内核输出数据(设备向主机传递数据)

  • 内核参数输入(主机向设备传递数据)

内核输出数据:

一般做法是将内存是将缓存对象设置为只写模式,这样设备只能写缓存,不能读缓存。这里可以将opetion参数设置为CL_MEM_ERITE_ONLY。 
注意:只写缓存,只有设备能够分配内存,主机不能。一次需要将主机指针参数设为NULL。 
内核参数输入:该情况是主机向设备传输数据,所以主机指针一定不能为NULL。此时需要设定缓存对象的数据分配地址。 
CL_MEM_USE_HOST_PTR表示缓存对象访问的内存和主机指针指向的内存空间相同。 
注意:这样看起来节省内存,但是主机和设备之间的数据传输将变得无法预测,甚至是在通信开始之后便无法访问这个主机内存所指向的内存。 
CL_MEM_COPY_HOST_PTR可以解决该问题。CL_MEM_COPY_HOST_PTR是指将主机指针所指向的区域的数据拷贝到新分配的内存中。这样虽然不节省内存但是却可以将主机主机指针指向的内存和主机设备间的数据传输操作分隔开,这样就不会产生干扰。 
CL_MEM_ALLOC_HOST_PTR只能和CL_MEM_COPY_HOST_PTR一同使用。CL_MEM_ALLOC_HOST_PTR可以将新内存空间限定为主机可访问。可能与固定内存相关,不太清楚。

9.设置内核参数

clSetKernelArg(cl_kernel kernel,cl_uint index,size_t size,const void *value);
  • 1
  • 2

最后一个指针参数指向的是传输给内核函数的数据。这个指针参数有以下几种形式:

  • 指向基本数据类型的指针——传输基本类型的数据到设备上
  • 指向内存对象的指针——传输重要或复杂的数据
  • 指向采样器对象的指针——传输配置读取图像的对象
  • NULL——没有来自主机的数据输入;设备将只为内存参数的指针地址保留相应的内存空间

10.执行内核

clEnqueueNDRangekernel(cl_command_queue queue,cl_kernel kernel,
                         cl_uint work_dims,
                         const size_t *global_work_offset, 
                         const size_t *global_work_size, 
                         const size_t *local_work_size, 
                         cl_uint num_events, 
                         const cl_event *wait_list, 
                         cl_event *event);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

11.读取内核结果

12.代码示例

host.c
  • 1
  • 2
#include<stdio.h>
#include<CL/cl.h>
#pragma warning( disable : 4996 )
int main() {
    cl_int error;
    cl_platform_id platforms;

    cl_device_id devices;

    cl_context context;

    FILE *program_handle;
    size_t program_size;
    char *program_buffer;
    cl_program program;

    size_t log_size;
    char *program_log;

    char kernel_name[] = "createBuffer";
    cl_kernel kernel;

    cl_command_queue queue;
    //获取平台
    error = clGetPlatformIDs(1, &platforms, NULL);
    if (error != 0) {
        printf("Get platform failed!");
        return -1;
    }
    //获取设备
    error = clGetDeviceIDs(platforms, CL_DEVICE_TYPE_GPU, 1, &devices, NULL);
    if (error != 0) {
        printf("Get device failed!");
        return -1;
    }
    //创建上下文
    context = clCreateContext(NULL,1,&devices,NULL,NULL,&error);
    if (error != 0) {
        printf("Creat context failed!");
        return -1;
    }
    //创建程序;注意要用"rb"
    program_handle = fopen("kernel.cl","rb");
    if (program_handle == NULL) {
        printf("The kernle can not be opened!");
        return -1;
    }
    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';
    error=fread(program_buffer,sizeof(char),program_size,program_handle);
    if (error == 0) {
        printf("Read kernel failed!");
        return -1;
    }
    fclose(program_handle);
    program = clCreateProgramWithSource(context,1,(const char **)&program_buffer, 
                                         &program_size,&error);
    if (error < 0) {
        printf("Couldn't create the program!");
        return -1;
    }
    //编译程序
    error = clBuildProgram(program,1,&devices,NULL,NULL,NULL);
    if (error < 0) {
        //确定日志文件的大小
        clGetProgramBuildInfo(program,devices,CL_PROGRAM_BUILD_LOG,0,NULL,&log_size);
        program_log = (char *)malloc(log_size+1);
        program_log[log_size] = '\0';
        //读取日志
        clGetProgramBuildInfo(program, devices, CL_PROGRAM_BUILD_LOG, 
                               log_size+1, program_log, NULL);
        printf("%s\n",program_log);
        free(program_log);
        return -1;
    }
    free(program_buffer);
    //创建命令队列
    queue = clCreateCommandQueue(context, devices, CL_QUEUE_PROFILING_ENABLE, &error);
    if (error < 0) {
        printf("Coudn't create the command queue");
        return -1;
    }
    //创建内核
    kernel = clCreateKernel(program,kernel_name,&error);
    if (kernel==NULL) {
        printf("Couldn't create kernel!\n");
        return -1;
    }
    //初始化参数
    float result[100];
    float a_in[100];
    float b_in[100];
    for (int i = 0; i < 100; i++) {
        a_in[i] = i;
        b_in[i] = i*2.0;
    }
    //创建缓存对象
    cl_mem memObject1 = clCreateBuffer(context,CL_MEM_READ_ONLY|CL_MEM_COPY_HOST_PTR,sizeof(float)*100,a_in,&error);
    if (error < 0) {
        printf("Creat memObject1 failed!\n");
        return -1;
    }
    cl_mem memObject2 = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, 
                                        sizeof(float) * 100, b_in, &error);
    if (error < 0) {
        printf("Creat memObject2 failed!\n");
        return -1;
    }
    cl_mem memObject3 = clCreateBuffer(context, CL_MEM_WRITE_ONLY , 
                                           sizeof(float) * 100, NULL, &error);
    if (error < 0) {
        printf("Creat memObject3 failed!\n");
        return -1;
    }
    //设置内核参数
    error = clSetKernelArg(kernel,0,sizeof(cl_mem),&memObject1);
    error|= clSetKernelArg(kernel, 1, sizeof(cl_mem), &memObject2);
    error |= clSetKernelArg(kernel, 2, sizeof(cl_mem), &memObject3);
    if (error != CL_SUCCESS) {
        printf("Error setting kernel arguments!\n");
        return -1;
    }
    //执行内核
    size_t globalWorkSize[1] = {100};
    size_t localWorkSize[1] = {1};
    error = clEnqueueNDRangeKernel(queue,kernel,1,NULL,globalWorkSize, 
                                    localWorkSize,0,NULL,NULL);
    if (error != CL_SUCCESS) {
        printf("Error queuing kernel for execution!\n");
        return -1;
    }
    //读取执行结果
    error = clEnqueueReadBuffer(queue,memObject3,CL_TRUE,0,100*sizeof(float), 
                                 result,0,NULL,NULL);
    if (error != CL_SUCCESS) {
        printf("Error reading result buffer!\n");
        return -1;
    }
    //显示结果
    for (int i = 0; i < 100; i++) {
        printf("%f ",result[i]);
    }
    //释放资源
    clReleaseDevice(devices);
    clReleaseContext(context);
    clReleaseCommandQueue(queue);
    clReleaseProgram(program);
    clReleaseKernel(kernel);
    clReleaseMemObject(memObject1);
    clReleaseMemObject(memObject2);
    clReleaseMemObject(memObject3);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
    kernel.cl
  • 1
  • 2
__kernel void createBuffer(__global const float *a_in,
    __global const float *b_in,
    __global float *result) {
int gid = get_global_id(0);
result[gid] = a_in[gid] + b_in[gid];
}

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

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

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

猜你喜欢

转载自blog.csdn.net/Song_Esther/article/details/80557351