在上一篇术语解释中,我们了解了 platform
,device
以及 context
的定义和一些相关的使用介绍。本篇将继续介绍一些术语,包括 program
、kernel
和 buffer
。
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 函数
的容器
ps:buffer
还是放在 memory
中说吧,单独讲的话略显片面单薄。