上下文为关联的设备、内存对象、命令队列、程序对象、内核对象提供一个容器。上下文是OpenCL应用的核心。正是上下文驱动着应用程序与特定设备以及特定设备之间的通信。
对于上下文中关联的所有计算设备必须全都来自于同一平台,对于来自不同平台的OpenCL设备,需要为各个平台独立地创建上下文。对于同一平台的设备,上下文中可以关联多个设备。主机应用也可以使用多个上下文来管理多个设备,甚至同一个平台多个设备都可以关联到不同的上下文,如下图所示。
在上图中,平台1有多个CPU和GPU设备。同一平台的设备可以关联到同一上下文中,所以上下文1、上下文2中关联CPU和GPU设备是可行的。
对于平台中的设备,上下文不是非要关联所有的设备,所以在上下文4中只是关联了部分设备。而不同平台的设备不能关联到同一上下文中,所以关联平台1中的GPU3和平台2中的CPU1的上下文3是不合法的。
OpenCL 创建上下文
OpenCL上下文对象用cl_context类型表示,可以使用如下两个函数其中之一来创建上下文:
cl_context clCreateContext(const cl_context_properties
*properties , cl_uint num_devices ,
const cl_device_id *devices ,
void (CL_CALLBACK *pfn_notify )
(const char *, const void *,
size_t, void *),
void *user_data ,
cl_int *errcode_ret )
cl_context clCreateContextFromType(const cl_context_properties
*properties,
cl_device_type device_type ,
void (CL_CALLBACK *pfn_notify )
(const char *, const void *,
size_t, void *),
void *user_data,
cl_int *errcode_ret)
两个函数用法很类似,主要的区别在于:
- clCreateContext显示地指定设备(devices)来创建上下文;
-
clCreateContextFromType根据给定的设备类型(device_type)来创建上下文。对设备类型详见下表。
参数properties指定上下文属性名称及属性相应的值的列表,且最后一个元素为0,见下表。当实现自定义选择平台时,参数properties可以设置为NULL。
参数pfn_notify和user_data用来共同定义一个回调函数,可以调用这个回调报告上下文生命周期中出现错误的有关信息,要把user_data作为最后一个参数传至回调函数。user_data为void类型指针,也就是可以指向任何数据,当错误发生时user_data能够提供信息。参数pfn_notf iy和user_data也都可以设置为NULL。参数errcode_ret为函数的返回状态,成功执行值为CL_SUCCESS,否则值为对应的错误代码。
下面的代码清单展示了给定一个平台,查询平台中GPU设备,并创建上下文:
cl_device_id *device;
cl_platform_id platform;
cl_int err;
cl_uint NumDevice;
//选择第一个平台
err = clGetPlatformIDs(1, &platform, NULL);
err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 0, NULL,
&NumDevice);
device = (cl_device_id *)malloc(sizeof(cl_device_id) *
NumDevice);
//选择GPU设备
err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, NumDevice,
device, NULL);
//创建上下文
cl_context_properties properites[] = {CL_CONTEXT_PLATFORM,
(cl_context_properties)platform,
0};
//指定设备创建上下文
//cl_context
context=clCreateContext(properites,NumDevice,device,NULL,NULL,&err);
//指定设备类型创建上下文
cl_context context = clCreateContextFromType(properites,
CL_DEVICE_TYPE_GPU,
NULL, NULL, &err);
例子中,clCreateContext函数把平台中查询到的所有GPU设备都关联到创建的上下文中。clCreateContextFromType则是选择第一平台中的GPU设备。从功能来说,两个函数实现的功能是一样的。
clGetContextInfo
给定上下文,可以使用如下函数查询上下文各个属性信息:
cl_int clGetContextInfo(cl_context context ,
cl_context_info param_name ,
size_t param_value_size ,
void *param_value ,
size_t *param_value_size_ret )
参数param_name为查询属性名称,取值见下表,参数param_value返回上下文查询属性信息。
下面的代码清单展示了使用clContextInfo()查询上下文关联的设备数目及上下文的引用计数方法:
cl_platform_id platform;
cl_int err;
cl_uint NumDevice;
err = clGetPlatformIDs(1, &platform, NULL);
cl_context_properties properites[] = {CL_CONTEXT_PLATFORM,
(cl_context_properties)platform, 0
};
cl_context context = clCreateContextFromType(properites,
CL_DEVICE_TYPE_ALL,
NULL, NULL, &err);
NumDevice = 0;
size_t DeviceSize;
err = clGetContextInfo(context, CL_CONTEXT_NUM_DEVICES,
sizeof(cl_uint), &NumDevice, NULL);
printf("Number of Device in context:%d\n", NumDevice);
上述代码片段中,在调用函数clContextInfo时,对其参数param_name指定的是CL_CONTEXT_NUM_DEVICES,从字面上理解非常直观,即我们要查询当前上下所包含的计算设备个数。除了可指定CL_CONTEXT_NUM_DEVICES这个属性以外,还能指定CL_CONTEXT_REFERENCE_COUNT这一属性,表示查询当前上下文的引用计数值。在之前我们没有讲解任何关于OpenCL中引用计数的概念,那这个引用计数到底是什么?它又有何作用呢?
在使用clCreateContext或clCreateContextFromType创建上下文时,并不像我们之前创建平台和设备时返回错误代码,而是直接返回cl_context对象,此时该上下文对象的引用计数为1。
其实,OpenCL对很多对象采用了类似于Apple的Cocoa Framework中的内存管理机制(毕竟OpenCL的初稿出自Apple)。在这种机制下,主张谁分配了某个对象,那么谁就负责释放该对象。如果这个对象不是由你来分配的,那么你也不用去释放它。我们通过OpenCL接口就能看出哪些对象是被分配的,哪些不是。例如,clGetPlatformIDs函数所获得的platform_id对象就不需要通过某个接口进行释放,因为它是通过Get获得的。
类似的是, clGetDeviceIDs也同样如此。这里的clCreateContext我们看到用的是Create这个前缀,当我们看到这个前缀时就要想到一定有一个与之相对应的Retain接口和Release接口。Retain接口用于显式地做引用计数加1操作;而Release则是显式地做引用计数减1操作,而只有当引用计数为0时,Release操作才会释放对应的空间。而引入这个引用计数机制对于第三方库或者跨模块的开发非常有利。例如,我们在模块A创建了一个上下文,然后在做完某个计算后把它提交给模块B继续计算。
当然,出于性能要求,模块B借用了这个上下文对象之后可能会自己另建一个命令队列然后执行,因此与模块A的后续操作完全可以是异步的。这就会引发一个问题,当模块A执行完之后,把该上下文对象释放掉,而模块B此时还在使用这个上下文对象。因此,如果没有引用计数,而是直接把此上下文对象释放掉,那么模块B在使用此对象时就可能会引发异常。而有了引用计数机制,我们可以遵循这个机制来做——在模块A中创建上下文对象,那么在A用完之后通过Release接口将它释放。如果要把此对象交给模块B做后续操作,那么要由模块B对此对象做一次Retain操作,然后模块B操作完成之后也要调用一次Release操作来释放此上下文对象。这样一来,上下文对象就能安全而又完整地被模块A与模块B共同使用了。
上下文对象的Retain和Release接口
对于上下文对象的Retain和Release接口如下:
cl_int clRetainContext(cl_context context )
cl_int clReleaseContext(cl_context context )
clRetainContext增加引用计数(引用计数+1),clReleaseContext减少引用计数(引用计数-1)。如果外部函数访问预先创建的上下文,确保在处理前调用clRetainContext,处理完成后调用clReleaseContext。如果在创建上下文的函数中,在函数完成前调用clReleaseContext来减少引用计数,使其值为0,释放上下文空间。
下面的代码展示了这两个函数的使用方式:
…
cl_context_properties properites[] = {CL_CONTEXT_PLATFORM,
(cl_context_properties)platform, 0
};
cl_context context = clCreateContextFromType(properites,
CL_DEVICE_TYPE_ALL, NULL, NULL, NULL);
cl_uint ReferenCount;
clGetContextInfo(context, CL_CONTEXT_REFERENCE_COUNT,
sizeof(cl_uint), &ReferenCount, NULL);
printf("Initial Reference Count: %d\n ", ReferenCount);
clRetainContext(context);
clGetContextInfo(context, CL_CONTEXT_REFERENCE_COUNT,
sizeof(cl_uint), &ReferenCount, NULL);
printf("Reference Count: %d\n ", ReferenCount);
clReleaseContext(context);
clGetContextInfo(context, CL_CONTEXT_REFERENCE_COUNT,
sizeof(cl_uint), &ReferenCount, NULL);
printf("Reference Count: %d\n ", ReferenCount);
如上代码,输出为:
Initial Reference Count:1
Reference Count:2
Reference Count:1