在OpenCL中使用ClenqueueEndRange内核
我需要OpenCL中一个函数的帮助。当我开始使用ClenqueueEndRangeKernel而不是clEnqueueTask时,程序需要更多的时间才能成功。为什么会这样?据我所知,该程序应该使用数据并行模型,它会工作得更快,我错了吗?如果是,我如何更改代码以查看数据并行模型的实际工作在OpenCL中使用ClenqueueEndRange内核,opencl,Opencl,我需要OpenCL中一个函数的帮助。当我开始使用ClenqueueEndRangeKernel而不是clEnqueueTask时,程序需要更多的时间才能成功。为什么会这样?据我所知,该程序应该使用数据并行模型,它会工作得更快,我错了吗?如果是,我如何更改代码以查看数据并行模型的实际工作 __kernel void black_white_img(__global unsigned char *pDataIn, __global unsigned char *pDataOut, unsigned
__kernel void black_white_img(__global unsigned char *pDataIn, __global unsigned char *pDataOut, unsigned int InSize, unsigned int OutSize)
{
for (int i = 0, j = 0; i < InSize; i+=4, j++)
{
unsigned char Value = (pDataIn[i] + pDataIn[i + 1] + pDataIn[i + 2]) / 3;
pDataOut[j] = Value;
}
}
int iWidth, iHeight, iBpp;
vector<unsigned char> pDataIn;
vector<unsigned char> pDataOut;
int err = LoadBmpFile(L"3840x2160.bmp", iWidth, iHeight, iBpp, pDataIn);
if (err != 0 || pDataIn.size() == 0 || iBpp != 32)
{
std::cout << "error load input file!\n";
}
pDataOut.resize(pDataIn.size()/4);
cl_device_id device_id = NULL;
cl_context context = NULL;
cl_command_queue command_queue = NULL;
cl_mem memobj = NULL;
cl_mem memobj1 = NULL;
cl_program program = NULL;
cl_kernel kernel = NULL;
cl_platform_id platform_id = NULL;
cl_uint ret_num_devices;
cl_uint ret_num_platforms;
cl_int ret;
unsigned int SizeIn, SizeOut;
SizeIn = pDataIn.size();
SizeOut = pDataOut.size();
FILE *fp;
char fileName[] = "./kernel.cl";
char *source_str;
size_t source_size;
//Loading kernel
fp = fopen(fileName, "r");
if (!fp) {
fprintf(stderr, "Failed to load kernel.\n");
system("PAUSE");
exit(1);
}
source_str = (char*)malloc(MAX_SOURCE_SIZE);
source_size = fread(source_str, 1, MAX_SOURCE_SIZE, fp);
fclose(fp);
//Getting Platform and Device
ret = clGetPlatformIDs(1, &platform_id, &ret_num_platforms);
ret = clGetDeviceIDs(platform_id, CL_DEVICE_TYPE_DEFAULT, 1, &device_id, &ret_num_devices);
//Create context
context = clCreateContext(NULL, 1, &device_id, NULL, NULL, &ret);
//create kernel program
program = clCreateProgramWithSource(context, 1, (const char **)&source_str,
(const size_t *)&source_size, &ret);
//build it
ret = clBuildProgram(program, 1, &device_id, NULL, NULL, NULL);
//create queue
command_queue = clCreateCommandQueue(context, device_id, 0, &ret);
//create bufer
memobj = clCreateBuffer(context, CL_MEM_READ_WRITE, pDataIn.size(), NULL, &ret);
memobj1 = clCreateBuffer(context, CL_MEM_READ_WRITE,pDataOut.size(), NULL, &ret);
//copy buffer to kernel
ret = clEnqueueWriteBuffer(command_queue, memobj, CL_TRUE, 0, pDataIn.size(), pDataIn.data(), 0, NULL, NULL);
//create opencl kernel
kernel = clCreateKernel(program, "red_to_green", &ret);
//set kernel args
ret = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&memobj);
ret = clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&memobj1);
ret = clSetKernelArg(kernel, 2, sizeof(unsigned int), (void *)&SizeIn);
ret = clSetKernelArg(kernel, 3, sizeof(unsigned int), (void *)&SizeOut);
const size_t cycles_max = 10;
clock_t t0 = clock();
for (int i = 0; i<cycles_max; i++){
float start_time = clock();
float search_time = 0;
//float last_time = 0;
//execute opencl kernel
//ret = clEnqueueTask(command_queue, kernel, 0, NULL, NULL);
size_t global_item_size = 8;
size_t local_item_size = 4;
ret = clEnqueueNDRangeKernel(command_queue,kernel, 1, NULL, &global_item_size, &local_item_size, 0, NULL, NULL);
//copy from buffer
ret = clEnqueueReadBuffer(command_queue, memobj1, CL_TRUE, 0, pDataOut.size(), pDataOut.data(), 0, NULL, NULL);
ret = clFinish(command_queue);
float end_time = clock();
search_time = end_time - start_time;
//float last_time = last_time + search_time;
cout << search_time << endl;
}
clock_t t1 = clock();
double time_seconds = (t1-t0)*CLOCKS_PER_SEC/cycles_max;
cout << time_seconds/1000 <<endl;
WriteBmpFile(L"3840x2160_wb.bmp", iWidth, iHeight, 8, pDataOut.size(), pDataOut.data(), false);
system("PAUSE");
内核使用单个工作项执行
clEnqueueTask相当于使用
工时尺寸=1,全局工时偏移=NULL,全局工时尺寸[0]设置为1,
本地工作大小[0]设置为1
当您使用ClenqueueEndRangeKernel时,您使用的是由4个工作项组成的2个工作组,但它们都在做相同的工作。它们都从相同的全局内存中读取,但更重要的是,它们都试图写入全局内存中的相同位置
在进行计算时,需要考虑工人的全局id
__kernel void black_white_img(__global unsigned char *pDataIn, __global unsigned char *pDataOut, unsigned int InSize, unsigned int OutSize)
{
int gid = get_global_id(0);
int gsize = get_global_size(0);
for (int j = gid; j < (InSize >> 2); j+= gsize)
{
unsigned char Value = (pDataIn[j*4] + pDataIn[j*4 + 1] + pDataIn[j*4 + 2]) / 3;
pDataOut[j] = Value;
}
}
内核使用单个工作项执行
clEnqueueTask相当于使用
工时尺寸=1,全局工时偏移=NULL,全局工时尺寸[0]设置为1,
本地工作大小[0]设置为1
当您使用ClenqueueEndRangeKernel时,您使用的是由4个工作项组成的2个工作组,但它们都在做相同的工作。它们都从相同的全局内存中读取,但更重要的是,它们都试图写入全局内存中的相同位置
在进行计算时,需要考虑工人的全局id
__kernel void black_white_img(__global unsigned char *pDataIn, __global unsigned char *pDataOut, unsigned int InSize, unsigned int OutSize)
{
int gid = get_global_id(0);
int gsize = get_global_size(0);
for (int j = gid; j < (InSize >> 2); j+= gsize)
{
unsigned char Value = (pDataIn[j*4] + pDataIn[j*4 + 1] + pDataIn[j*4 + 2]) / 3;
pDataOut[j] = Value;
}
}
看起来您正在迭代内核中输入图像的所有像素。这将导致所有线程计算所有像素的图像强度。尝试为每个像素启动一个线程。为此,请将内核源代码更改为仅计算一个像素的输出值:
__kernel void black_white_img(__global unsigned char *pDataIn, __global unsigned char *pDataOut) {
int j = get_global_id(0);
int i = j*4;
pDataOut[i] = (pDataIn[j] + pDataIn[j + 1] + pDataIn[j + 2]) / 3;
}
此代码现在将对位置i处的单个像素的RGBA输入图像的RGB值执行平均。现在,您需要做的就是启动尽可能多的线程,因为您的图像有像素。相关变化:
//create opencl kernel
kernel = clCreateKernel(program, "black_white_img", &ret);
//set kernel args
ret = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&memobj);
ret = clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&memobj1);
const size_t cycles_max = 10;
clock_t t0 = clock();
for (int i = 0; i<cycles_max; i++){
float start_time = clock();
float search_time = 0;
//float last_time = 0;
//execute opencl kernel
//ret = clEnqueueTask(command_queue, kernel, 0, NULL, NULL);
size_t global_item_size = iWidth * iHeight;
ret = clEnqueueNDRangeKernel(command_queue,kernel, 1, NULL, &global_item_size, NULL, 0, NULL, NULL);
与您的代码相比,这将提供相当大的加速 看起来您正在迭代内核中输入图像的所有像素。这将导致所有线程计算所有像素的图像强度。尝试为每个像素启动一个线程。为此,请将内核源代码更改为仅计算一个像素的输出值:
__kernel void black_white_img(__global unsigned char *pDataIn, __global unsigned char *pDataOut) {
int j = get_global_id(0);
int i = j*4;
pDataOut[i] = (pDataIn[j] + pDataIn[j + 1] + pDataIn[j + 2]) / 3;
}
此代码现在将对位置i处的单个像素的RGBA输入图像的RGB值执行平均。现在,您需要做的就是启动尽可能多的线程,因为您的图像有像素。相关变化:
//create opencl kernel
kernel = clCreateKernel(program, "black_white_img", &ret);
//set kernel args
ret = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&memobj);
ret = clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&memobj1);
const size_t cycles_max = 10;
clock_t t0 = clock();
for (int i = 0; i<cycles_max; i++){
float start_time = clock();
float search_time = 0;
//float last_time = 0;
//execute opencl kernel
//ret = clEnqueueTask(command_queue, kernel, 0, NULL, NULL);
size_t global_item_size = iWidth * iHeight;
ret = clEnqueueNDRangeKernel(command_queue,kernel, 1, NULL, &global_item_size, NULL, 0, NULL, NULL);
与您的代码相比,这将提供相当大的加速 对于克伦奎茨克,我使用了一个工作组和一个工作项,对吗?说清楚,是的。谢谢你的回答!但它仍然比使用克伦奎茨克慢50毫秒,而不是20-30毫秒。还好吧,还是我做错了什么?您还可以解释一下全局项目大小和局部项目大小是如何工作的,以及为什么我们在这个函数中需要它们,而在clEnqueueTask中不需要它们?请尝试一个工作组,即:全局项目大小=局部项目大小=64。另外,确保InSize是大的-我会选择至少10k,但100多万会更好。在有大量工作要做之前,并行性不会很明显。尝试过,但仍然工作得比较慢。也许你可以给我一些链接,让我可以更深入地研究这个主题?无论如何,非常感谢!对于克伦奎茨克,我使用了一个工作组和一个工作项,对吗?说清楚,是的。谢谢你的回答!但它仍然比使用克伦奎茨克慢50毫秒,而不是20-30毫秒。还好吧,还是我做错了什么?您还可以解释一下全局项目大小和局部项目大小是如何工作的,以及为什么我们在这个函数中需要它们,而在clEnqueueTask中不需要它们?请尝试一个工作组,即:全局项目大小=局部项目大小=64。另外,确保InSize是大的-我会选择至少10k,但100多万会更好。在有大量工作要做之前,并行性不会很明显。尝试过,但仍然工作得比较慢。也许你可以给我一些链接,让我可以更深入地研究这个主题?无论如何,非常感谢!