OpenCL 内核不向主机程序返回字符数据
Posted
技术标签:
【中文标题】OpenCL 内核不向主机程序返回字符数据【英文标题】:OpenCL kernel does not return character data to the host program 【发布时间】:2020-10-07 11:39:50 【问题描述】:我是 OpenCL 新手,向 Matthew Scarpino's “OpenCL in Action” 学习。我研究了一个向量矩阵乘法的例子(第 11-13 页)。由于某种原因,该示例无法在我的计算机上运行。内核没有返回值。我开始寻找从内核输出数据的简单例子。
我在 Wesley Shillingford 的 youtube 频道中发现 example 输出字符串“Hello world!”从内核。在我的家用电脑上,该示例有效。但是,由于示例是用 C++ 编写的,因此 OpenCL“厨房”一直处于关闭状态。代码的简洁掩盖了正在发生的事情的概念。于是我开始进一步寻找 C 代码中的示例。
在 *** 上的答案中,我发现了一个 example of a minimal OpenCL program,它增加了内核中的值。我把这段代码作为编写程序的基础,因为它对初学者来说简单方便。后来我发现,该示例包含错误。
另一个great example 说服我使用指针从内核返回数据。使用数组存储内核的输出值会导致目标数组的值不会改变,并且来自内核的数据在输出过程中会消失。 我意识到我们需要使用指针来从内核输出数据。但是,这对我没有帮助。将数据从内核传输到主机程序的问题仍然存在。如果我在某些方面有误,请纠正我。题目精髓:内核不向宿主程序返回字符数据。可能是什么问题?
#include <CL/cl.h>
#include <stdio.h>
#include <stdlib.h>
int main()
cl_platform_id *platforms =NULL;
cl_device_id *devices=NULL;
cl_context context;
cl_command_queue cmdQueue;
cl_program program;
cl_kernel kernel = NULL;
char *cpOutputData;
int output_size = 8;
cl_mem output_buff;
cl_int status; // to check the output of each API call
const char *source =
"__kernel void Hello( __global char* ch) \n"
" ch[0]='P';"
" ch[1]='r';"
" ch[2]='i';"
" ch[3]='v';"
" ch[4]='e';"
" ch[5]='t';"
" ch[6]='!';"
" ch[7]='\0';"
"\0";
printf("GetPlatformIDs... ");
cl_uint numPlatforms = 0;
//STEP 1: Discover and initialize platforms
// Use clGetPlatformIDs to retreive the number of platforms
status = clGetPlatformIDs(0,
NULL,
&numPlatforms);
// Allocate enough space for each platform
platforms = (cl_platform_id*)malloc(numPlatforms*sizeof(cl_platform_id));
// Fill in platforms with clGetPlatformIDs()
status=clGetPlatformIDs(numPlatforms,
platforms,
NULL);
printf("\nNumber of discovered platforms is %d. ", numPlatforms);
// STEP 2: Discover and initialize devices
printf("OK.\nGetDeviceIDs... ");
cl_uint numDevices = 0;
// Use clGetDeviceIDs() to retrieve the number of devices present
status = clGetDeviceIDs(platforms[0],
CL_DEVICE_TYPE_CPU,
0,
NULL,
&numDevices);
// Allocate enough space for each device
devices = (cl_device_id*)malloc(numDevices*sizeof(cl_device_id));
// Fill in devices with clGetDeviceIDs()
clGetDeviceIDs(platforms[0],
CL_DEVICE_TYPE_CPU,
numDevices,
devices,
NULL);
printf("\nNumber of discovered devices is %d. ", numDevices);
// STEP 3: Create a context
printf("OK.\nCreating context... ");
// Create context using clCreateContext() and associate it with the devices
context = clCreateContext(NULL,
numDevices,
devices,
NULL,
NULL,
&status);
// STEP 4: Create a command queue
printf("OK.\nQueue creating... ");
cmdQueue = clCreateCommandQueue(context,
devices[0],
CL_QUEUE_PROFILING_ENABLE,
&status);
// STEP 5: Create device buffers
printf("OK.\nOutput buffer creating... ");
output_buff = clCreateBuffer(context,
CL_MEM_WRITE_ONLY,
sizeof(char)*output_size,
NULL,
&status);
// STEP 6: Create and compile program
printf("OK.\nBuilding program... ");
// Create a program using clCreateProgramWithSource()
program = clCreateProgramWithSource(context,
1,
(const char**)&source,
NULL,
&status);
// Build (compile) the program for the devices with clBuildProgram()
status=clBuildProgram(program,
numDevices,
devices,
NULL,
NULL,
NULL);
// STEP 7: Create a kernel
printf("OK.\nCreating kernel... ");
kernel = clCreateKernel(program,
"Hello",
&status);
// STEP 8: Set kernel arguments
// Associate ouput buffer with the kernel
printf("OK.\nSetting kernel arguments... ");
status = clSetKernelArg(kernel,
0,
sizeof(cl_mem),
&output_buff);
// STEP 9: Configure the work-item structure
// Define an index space (global work size) of work itmes for execution.
// A workgroup size (local work size) is not required, but can be used.
size_t globalWorkSize[1];
// There are 'elements' work-items
globalWorkSize[0] = output_size;
// STEP 10: Enqueue the kernel for execution
printf("OK.\nExecuting kernel... ");
//Execute the kernel by using clEnqueueNDRangeKernel().
// 'globalWorkSize' is the 1D dimension of the work-items
clEnqueueNDRangeKernel(cmdQueue,
kernel,
1,
NULL,
globalWorkSize,
NULL,
0,
NULL,
NULL);
clFinish(cmdQueue);
// STEP 11: Read the ouput buffer back to the host
printf("OK.\nReading buffer... ");
// Allocate space for the data to be read
cpOutputData = (char*)malloc(output_size*sizeof(char));
// Use clEnqueueReadBuffer() to read the OpenCL ouput buffer to the host ouput array
clEnqueueReadBuffer(cmdQueue,
output_buff,
CL_TRUE,
0,
output_size,
cpOutputData,
0,
NULL,
NULL);
printf("\nPrinting output data: \n");
printf(cpOutputData);
// STEP 12: Releasing resources
printf("\n...Releasing OpenCL resources... ");
clReleaseKernel(kernel);
clReleaseProgram(program);
clReleaseCommandQueue(cmdQueue);
clReleaseMemObject(output_buff);
clReleaseContext(context);
printf("OK.\n...Releasing host resources... ");
free(cpOutputData);
free(platforms);
free(devices);
printf("OK.\nEnd of program. Bey!\n");
system("PAUSE");
return 0;
我的程序的执行输出is here。
【问题讨论】:
感谢您在 Stack Overflow 上发布这样一个格式正确的问题作为您的第一个问题!接听来电。 【参考方案1】:您遇到的问题非常微妙,不幸的是,您没有在会发现它的地方进行错误检查。具体来说,使用clBuildProgram
编译内核源代码失败,不幸的是status
没有被检查。我不确定为什么该程序的其余部分不会在您的实现中产生错误,它在我的实现中肯定会产生。
您的内核源代码无效的原因是这一行:
" ch[7]='\0';"
// ^^---- This terminates the string early!
基本上,您的内核源代码在 OpenCL 编译器中看起来像这样:
__kernel void Hello( __global char* ch)
ch[0]='P';
ch[1]='r';
ch[2]='i';
ch[3]='v';
ch[4]='e';
ch[5]='t';
ch[6]='!';
ch[7]='
因为字符串文字中的转义码\0
会在source
变量最终指向的内存中插入一个实际的nul 字符,导致它被视为内核源代码的结尾。
您真正想要的是转义序列出现在 OpenCL 内核的代码中,因此您需要转义两次:一次用于主机程序的 C 编译器,第二次用于 OpenCL 编译器。那将是:
" ch[7]='\\0';"
// ^--- note second backslash
双反斜杠在source
字符串中被转换为单反斜杠,OpenCL 编译器将其与随后的零结合起来,将字符文字转换为空字符。
有了这个修复,一切正常!
我建议在单独的文件中编写内核源代码。在您的程序中使用文件 I/O 加载该文件,或者为数据自动生成文字以嵌入到您的源代码中。 unix tool xxd
可以使用 -i
标志执行此操作,您可能会找到 Windows 等效项,甚至是该工具本身的 Windows 版本。
【讨论】:
以上是关于OpenCL 内核不向主机程序返回字符数据的主要内容,如果未能解决你的问题,请参考以下文章