Opencl基本术语二

来源:互联网 发布:sqlserver 进阶 编辑:程序博客网 时间:2024/05/24 22:45

在上一篇术语解释中,我们了解了 platformdevice 以及 context 的定义和一些相关的使用介绍。本篇将继续介绍一些术语,包括 programkernel 和 buffer

  • program
  • kernel
  • 总结

program

program 的意思相信大家都懂的,毕竟我们自己就是一名苦逼的 programmer。顾名思义,program 代表的是一个程序对象,里面包含着我们所写的 cl 程序。

做 OpenCL 编程,要写两种代码,一种是 host 程序,一种是 cl 程序,类比于图形编程,cl 程序就等同于 shader 程序。host 端的代码干的是为 cl 程序设置环境,打扫内存等保姆型工作, cl 程序才是真正 解决问题 的地方。

要让 cl 程序发挥作用,得先让 host 端把它送进 device 是吧。送之前得先加载进host 吧。加载后存哪呢?存在 program 里面!!

创建一个 program 对象有两种方法,一种是利用 clCreateProgramWithSource 函数从 cl源代码创建,另一种是利用 clCreateProgramWithBinary 函数从二进制中创建。两个函数的原型如下:

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

关于两种创建方式实际使用,我后面会写个小示例。当然,OpenCL Specification 一直都是最佳最标准的参考读物。

一般创建好一个 program 对象后,接着就是对其进行编译。从上面两个创建函数可以看出,现在 cl 程序存在 program 对象中,其形式莫过于源代码二进制。而我们的device (无论是CPU、GPU还是DSP)能处理的仅仅是机器码和指令。所以与一般的编程相同,我们需要将 cl 程序进行编译。不同之处在于编译方法不在是点一下IDE上的按钮或者make一下,而是在 host 程序内部以 API 调用的方式来控制 编译。控制编译的 API 是 clBuildProgram,其原型如下:

cl_int clBuildProgram(cl_program            program,                   cl_uint               num,                   const cl_device_id    *device_list,                   const char            *options,                   void(CL_CALLBACK *pfn_notify)(cl_program program,                                                 void *user_data),                   void                  *user_data )

这个函数会把装有 cl 程序的 program 对象传入指定的 device(第三个参数)进行编译。而且,你也可以设定多种编译 options(第四个参数)。options 包括预处理器定义和各种优化以及代码生成(比如,-DUSE_FEATURE = 1 -cl -mad -enable)。

编译完成并最终生成的可执行代码还是存在了指定 device(第三个参数指定的)的program 中。如果在所有指定 device 上编译成功,则该函数返回 CL_SUCCESS;否则会返回一个错误码。如果存在错误,可以调用 clGetProgramBuildInfo,并指定param_name(第三个参数) 为 CL_PROGRAM_BUILD_LOG来查看某个 device(第二个参数)上的详细构建日志。

cl_int clGetProgramBuildInfo( cl_program             program,                          cl_device_id           device,                          cl_program_buld_info   param_name,                          size_t                 param_value_size,                          void                   *param_value,                          size_t                 *param_value_size_ret )

这类由我们自己 create 的对象,可以手动控制其引用计数,对象使用完了一定要记得release。不然内存泄漏是很惨的。

cl_int clRetainProgram( cl_program program )    // 递减引用计数cl_int clReleaseProgram(cl_program program )    // 递增引用计数

kernel

如果说上述 program 对象是一个容器,装着一个 cl 程序,那么一个 kernel 对象也是一个容器,装的是 cl 程序中的一个 kernel 函数

kernel 函数指的是 cl 程序中那些以 kernel 或 __kernel 限定的函数。一段 cl 代码可以包含多个 kernel 函数,自然就对应着多个 kernel` 对象。

kernel 对象有什么用呢?想想我们要执行某个 kernel 函数,要传递一堆的参数,在平时的C编程里面,直接调用函数即可是吧。可问题在于现在 host 端与 cl 端是分离的,不光文件分离,甚至连执行的地方都是分离的(比如,host 在CPU上,cl 在GPU上)。这种情况下怎么向 kernel 函数 传递参数呢?

我们的 kernel 对象的用处就在于此:参数传递(数据传递)。我们一直说 OpenCL 提供了一个异构计算的平台,这些各种对象其实就是对异构编程的一个抽象。

了解了 what 和 why,接下来就是 how了。

创建 kernel 对象的方法是将 kernel 函数名(第二个参数) 传入 clCreateKernel

cl_kernel clCreateKernel( cl_program program,                      const char *kernel_name,                      cl_int     *errcode_ret )

kernel_name 指的就是 cl 代码里面 kernel 或 __kernel 关键字后面的函数名。

clCreateKernel 一次只能为一个 kernel 函数 创建一个 kernel 对象。如果你的 cl 代码里面有很多很多 kernel 函数 怎么办?一个个来?那还不烦死!所以这种情况请使用clCreateKernelsInProgram 为 program 中的所有 kernel 函数 创建对象。

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

使用 clCreateKernelsInProgram 时需要调用两次:第一次确定 kernel 数量;然后申请空间;第二次具体创建 kernel 对象。比如下面的示例代码:

cl_uint numKernels;clCreateKernelsInProgram( program, 0, NULL, &numKernels );  // 第一次调用确定数量cl_kernel *kernels = new cl_kernel[numKernels];             // 根据数量申请空间clCreateKernelsInProgram( program, numKernels, kernels, &numKernels );  // 具体创建对象

创建好之后,就可以为相应的 kernel 函数 传递参数了。使用的 API 是 clSetKernelArg

cl_int clSetKernelArg( cl_kernel     kernel,                   cl_uint       arg_index,                   size_t        *arg_size,                   const void    *arg_value )

其中,arg_index 是从 0 开始的,即第一个参数索引为 0,第二个为 1,依次类推。

最后当然得记得增减计数器:

cl_int RetainKernel( cl_kernel kernel )cl_int ReleaseKernel(cl_kernel kernel )

注意:有时我们会考虑对一个 program 对象重新编译。记住,重新编译前一定要把与该 program 对象相关的 kernel 对象全部释放掉,否则重新编译会出错!!!


总结

  • program:装着一段完整 cl 代码的容器
  • kernel :装着 cl 代码中一个 kernel 函数 的容器
0 0