OpenCL入门二:OpenCL基础概念

基础概览

原理建议阅读下面文章,文中介绍了OpenCL相关名词概念:
https://www.cnblogs.com/yxwkf/p/4552029.html 简介
http://opencl.codeplex.com/wikipage?title=OpenCL Tutorials - 1 (英文版) http://www.cnblogs.com/leiben/archive/2012/06/05/2536508.html (博友翻译的中文版)

在这里插入图片描述

用GPU计算两个数组之和

OpenCL支持德平台、设备很多,为了兼顾不同设备,OpenCL程序的第一步就是确定OpenCL执行的平台,在确定平台之后再确定执行OpenCL计算的设备。确定设备后创建上下文,上下文中包含上一步查询的OpenCL计算的设备(允许包含多个计算设备对象),以及接下来创建的内核、程序对象和内存对象。创建上下文以后,需要创建命令队列,但一个OpenCL设备可以对应多个队列。例如,如果需要使用上下文中包含的两个计算设备时,为每个设备创建各自的命令队列。主机与OpenCL设备间数据传输、执行内核等交互操作都是通过入队到命令队列中,命令队列中的各个命令交给OpenCL驱动或相应的硬件单元去执行。由于在运行时才知道OpenCL设备信息,所以在OpenCL主机端程序中读取内核源码并创建程序对象,根基OpenCL设备编译、构建程序对象,最后创建内核对象。通过这三步操作,把OpenCL设备上执行的内核代码编译完成。对于在OpenCL设备上执行的内核函数需要输入参数,在主机端调用设置内核参数函数。除此之外,还需要设置在设备上用于执行的工作组和工作项的参数。当上述操作都完成以后,我们就可以把内核函数入队到命令队列中,命令队列提交给相应的设备执行。OpenCL设备执行完计算后,把数据拷贝回主机端,并销毁分配的资源。

cl_context context = 0;
cl_command_queue commandQueue = 0;
cl_program program = 0;
cl_device_id device = 0;
cl_kernel kernel = 0;
cl_mem memObjects[3] = { 0, 0, 0};
cl_int errNum;

// 创建OpenCL上下文
context = CreateContext(&device);
//获得OpenCL设备,并创建命令队列
commandQueue = CreateCommandQueue(context, device);
// 创建OpenCL程序
program = CreateProgram(context, device, "device.cl");
// 创建OpenCL内核
kernel = clCreateKernel(program, "vector_add", NULL);
// 创建OpenCL内存对象
float result[ARRAY_SIZE];
float a[ARRAY_SIZE];
float b[ARRAY_SIZE];
for (int i = 0; i < ARRAY_SIZE; i++)
{
	a[i] = (float)i;
	b[i] = (float)(i * 2);
} 
if (!CreateMemObjects(context, memObjects, a, b))
{
	return 1;
}
// 设置内核参数
errNum = clSetKernelArg(kernel, 0, sizeof(cl_mem), &memObjects[0]);
errNum |= clSetKernelArg(kernel, 1, sizeof(cl_mem), &memObjects[1]);
errNum |= clSetKernelArg(kernel, 2, sizeof(cl_mem), &memObjects[2]);
if (errNum != CL_SUCCESS)
{
	return 1;
}
// 执行内核
size_t gloabalWorkSize = ARRAY_SIZE;
 size_t localWorkSize = 1;
 std::cout << "GPU 运行开始:" << time_stamp() << std::endl;
 errNum = clEnqueueNDRangeKernel(commandQueue, kernel, 1, NULL, &gloabalWorkSize, &localWorkSize, 0, NULL, NULL);
 std::cout << "GPU 运行结束:" << time_stamp() << std::endl;
//计算结果拷贝回主机
errNum = clEnqueueReadBuffer(commandQueue, memObjects[2], CL_TRUE, 0, sizeof(float) * ARRAY_SIZE, result, 0, NULL, NULL);
for (int i = 0; i < ARRAY_SIZE; i++)
 {
  printf("i = %d:%f\n",i,result[i]);
 }

device.cl代码:

__kernel void vector_add(global const float *a, global const float *b, global float *result)
{ 
 int gid = get_global_id(0);
 result[gid] = a[gid] + b[gid];
}

执行结果:
在这里插入图片描述
对于上下文中关联的所有计算设备必须全都来自于同一平台,对于来自不同平台的OpenCL设备,需要为各个平台独立地创建上下文。对于同一平台的设备,上下文中可以关联多个设备。主机应用也可以使用多个上下文来管理多个设备,甚至同一平台多个设备都可以关联到不同的上下文。

cl_context CreateContext(cl_device_id *device)
{
	cl_int errNum;
 	cl_uint numPlatforms;
 	cl_platform_id firstPlatformId;
 	cl_context context = NULL;
 	cl_uint count;
 	errNum = clGetPlatformIDs(1, &firstPlatformId, &numPlatforms);
	if (errNum != CL_SUCCESS || numPlatforms <= 0)
 	{
  		printf("Failed to find any OpenCL platforms.");
  		return NULL;
 	}
 	std::cout << "Number of available platforms: " << numPlatforms << std::endl;
 	errNum = clGetDeviceIDs(firstPlatformId, CL_DEVICE_TYPE_GPU, 1, device, &count);
 	std::cout << " " << "CL_DEVICE_TYPE_GPU" << ": " << count << std::endl;
 	if (errNum != CL_SUCCESS)
 	{
		printf("There is no GPU, trying CPU...");
  		errNum = clGetDeviceIDs(firstPlatformId, CL_DEVICE_TYPE_CPU, 1, device, &count);
  		std::cout << " " << "CL_DEVICE_TYPE_CPU" << ": " << count << std::endl;
	}
	if (errNum != CL_SUCCESS)
 	{
  		printf("There is NO GPU or CPU");
		return NULL;
	 }
	 context = clCreateContext(NULL, 1, device, NULL, NULL, &errNum);
 	if (errNum != CL_SUCCESS)
	{
  		printf("create context error\n");
		return NULL;
 	}
 	 return context;
}

命令提交到命令队列中,命令队列把需要执行的命令发送给设备。需要注意的是,每条命令队列只能关联一个设备,如果要同时使用多个设备,则需要创建多个命令队列,每个命令队列关联到一个设备。命令队列中的命令,只能是主机发送给设备,而设备不能发送命令给主机。

cl_command_queue CreateCommandQueue(cl_context context, cl_device_id device)
{
	cl_int errNum;
	cl_command_queue commandQueue = NULL;
	commandQueue = clCreateCommandQueue(context, device, 0, NULL);
	if (commandQueue == NULL)
 	{
  		printf("Failed to create commandQueue for device 0");
  		return NULL;
	}
 return commandQueue;
}

程序对象与内核对象是OpenCL中两个最重要的对象,这两个对象关系到设备上执行的代码,以及代码执行所实现的功能。对于内核对象而言,一个内核对象代表的就是一个在设备上执行的函数;而对于程序对象而言,一个程序对象就是内核的一个容器。打个比方:程序对象就好比一个集群系统,内核对象就好比在这个集群系统,内核对象就好比在这个集群系统中的节点,多个节点组成了这个集群系统,同时对于每个节点而言可以执行不同的任务(程序对象里的每个内核函数实现功能不同)。换句话说,我们通常会在一个.cl文件里写上多个内核函数,甚至写上多个.cl文件,包含多个内核函数。一个程序对象包含了对所有指定cl源文件编译、构建后的一个二进制目标对象。而一个内核对象则表示其中某一个内核执行函数。

char* ReadKernelSourceFile(const char* filename, size_t* length)
{
	FILE *file = NULL;
 	size_t sourceLength;
 	char *sourceString;
 	int ret;
 	file = fopen(filename, "rb");
 	if (file == NULL)
 	{	
  		printf("%s at %d: can't open %s\n", __FILE__, __LINE__ - 2, filename);
  		return NULL;
 	}
 	fseek(file, 0, SEEK_END);
 	sourceLength = ftell(file);
 	fseek(file, 0, SEEK_SET);
 	sourceString = (char *)malloc(sourceLength + 1);
 	sourceString[0] = '\0';
 	ret = fread(sourceString, sourceLength, 1, file);
 	if (ret == 0)
 	{	
  		printf("%s at %d: Can't read source %s\n", __FILE__, __LINE__ -2, filename);
  		return NULL;
 	}
 	fclose(file);
 	if (length != 0)
 	{
  		*length = sourceLength;
 	}
 	sourceString[sourceLength] = '\0';
 	return sourceString;
}

cl_program CreateProgram(cl_context context, cl_device_id device, const char *filename)
{
	cl_int errNum;
 	cl_program program;
 	size_t program_length;
 	char* const source = ReadKernelSourceFile(filename, &program_length);
 	program = clCreateProgramWithSource(context, 1, (const char **)&source, NULL, NULL);
 	if (program == NULL)
 	{
  		printf("Failed to create CL program from source.");
  		return NULL;
 	}
 	errNum = clBuildProgram(program, 0, NULL, NULL, NULL, NULL);
 	if (errNum != CL_SUCCESS)
 	{
  		char buildLog[16384];
  		clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, sizeof(buildLog), buildLog, NULL);
  		printf("Error in kernel:%s ", buildLog);
  		clReleaseProgram(program);
  		return NULL;
 	}
 	return program;
}
bool CreateMemObjects(cl_context context, cl_mem memObjects[3], float *a, float *b)
{
 memObjects[0] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float) * ARRAY_SIZE, a, NULL);
 memObjects[1] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float) * ARRAY_SIZE, b, NULL);
 memObjects[2] = clCreateBuffer(context, CL_MEM_WRITE_ONLY, sizeof(float) * ARRAY_SIZE, NULL, NULL);
 if (memObjects[0] == NULL || memObjects[1] == NULL || memObjects[2] == NULL)
 {
  printf("Error creating memory objects.");
  return false;
 }
 return true;
}
double time_stamp()
{
	LARGE_INTEGER curclock;
 	LARGE_INTEGER freq;
 	if (
  !QueryPerformanceCounter(&curclock) ||
  !QueryPerformanceFrequency(&freq)
  )
  {
  return -1;
 }
 return double(curclock.QuadPart) / freq.QuadPart;
}

猜你喜欢

转载自blog.csdn.net/asmartkiller/article/details/86611145