当在循环内启动大量内核时,OpenCL程序会冻结

时间:2013-10-08 12:57:08

标签: c opencl nvidia

我有一个启动OpenCL内核的循环(大约10亿次迭代)。每个内核由1个线程执行,并执行非常简单的操作。问题是在执行了几百万次迭代之后,代码冻结(停止)并且程序根本不会终止。它在对clFinish()的调用中冻结。该程序并不总是在同一次迭代中冻结。

如果clFinish()每1000次迭代调用一次而不是在每次迭代中调用,问题就会消失,所以我觉得问题是clFinish()正在等待内核的结束但kernl被杀死了(某种程度上)在调用clFinish()之前。另请注意,当我在循环内插入许多printf()调用时,问题就会消失!

我在CPU设备上执行程序时遇到问题(在我的笔记本电脑上,我使用的是AMD SDK),我在使用Nvidia Fermi GPU的机器上也遇到了问题(Nvidia SDK和驱动程序,还安装了AMD SDK)在那台机器上。)

我在每次OpenCL API调用后检查错误,但未检测到错误。我删除了错误检查以使代码清晰。

我的问题:

  • 下面是否正确使用OpenCL API?

  • 如果同时启动大量OpenCL内核会有任何问题吗?

代码是由我们的一些工具自动生成的,所以请不要问我为什么只用一个线程调用内核(这是另一个问题,我知道这样的代码不利于性能)。我的目标是了解代码中的问题是什么,理论上应该没有任何问题。

主机代码:

/* OpenCL initialization.  */
/* ... */
cl_mem dev_acc = clCreateBuffer(context, CL_MEM_READ_WRITE, sizeof(double), NULL, &err);

for (int h0 = 1; h0 <= ni; h0 += 1)
  for (int h2 = 0; h2 < nj; h2 += 1)
    for (int h5 = 0; h5 < h2 - 1; h5 += 1) {
          size_t global_work_size[1] = {1};
          size_t block_size[1] = {1};
          cl_kernel kernel2 = clCreateKernel(program, "kernel2", &err);
          clSetKernelArg(kernel2, 0, sizeof(cl_mem), (void *) &dev_acc);
          clEnqueueNDRangeKernel(queue, kernel2, 1, NULL, global_work_size,block_size,0, NULL, NULL);
          clFinish(queue);
          clReleaseKernel(kernel2);
       }

内核代码:

__kernel void kernel2(__global double *acc)
{
   *acc = 1;
}

汇编: gcc -O3 -lm -std = gnu99 polybench.c ocl_utilities.c symm_host.c -lOpenCL -lm -I / opt / AMDAPP / include -L / opt / AMDAPP / lib / x86_64

我正在使用Ubuntu 12.04,内核3.2.0-29-通用,X86_64,RAM:2 GB

2 个答案:

答案 0 :(得分:4)

好吧,看看你的代码,我甚至不知道从哪里开始...

但是,如果涉及到OpenCL标准,它应该运行良好。如果您正在使用的库的实现能够处理这个问题。

您应该做的第一件事是检查每个OpenCL API调用的错误代码。我认为你“过度填充”了你的命令队列,并从OpenCL libarry得到了无助的尖叫,没有人听到。如果你使用clFinish,队列会不时地清空,可能会阻止这种“过度填充”。

其他一些事情: 单个内核真的是你想要的吗? OpenCL旨在执行SIMD架构,即单个指令多个数据。因此,当大量线程在不同数据上执行相同的代码时,OpenCL的性能最佳。

您不必每次都在循环中创建kerneal:

size_t global_work_size[1] = {1};
size_t block_size[1] = {1};
cl_kernel kernel2 = clCreateKernel(program, "kernel2", &err);
for (int h0 = 1; h0 <= ni; h0 += 1)
  for (int h2 = 0; h2 < nj; h2 += 1)
    for (int h5 = 0; h5 < h2 - 1; h5 += 1) {
          clSetKernelArg(kernel2, 0, sizeof(cl_mem), (void *) &dev_acc);
          clEnqueueNDRangeKernel(queue, kernel2, 1, NULL, global_work_size,block_size,0, NULL, NULL);
          clFinish(queue);

       }
clReleaseKernel(kernel2);

最后一件事是你的执行模式只有一个线程:

如果可能的话尝试类似的东西(我不知道你对记忆的要求等等):

cl_mem dev_acc = clCreateBuffer(context, CL_MEM_READ_WRITE, ni * nj * sizeof(double), NULL, &err);

size_t global_work_size[1];
global_work_size[0] = ni;
global_work_size[1] = nj;
size_t block_size[1] = {1};
cl_kernel kernel2 = clCreateKernel(program, "kernel2", &err);

// some loop
clSetKernelArg(kernel2, 0, sizeof(cl_mem), (void *) &dev_acc);
clSetKernelArg(kernel2, 1, sizeof(int), &h2);
clEnqueueNDRangeKernel(queue, kernel2, 1, NULL, global_work_size,block_size,0, NULL, NULL);

一个看起来像某个内核的内核:

__kernel void kernel2(__global double *acc, int h5)
{
   int h0 = get_global_id(0);
   int h2 = get_global_id(1);
   int ni = get_global_size(0);
   int nj = get_global_size(1);
   // do stuff with ni, nj, h0, h2
   if (h5 < h2)
   {
      *acc = 1;
   }
}

答案 1 :(得分:0)

kronos有一些很好的反馈,只是为了增加更多的输入:

  1. 内核可以加载和编译一次,每个设备使用多次。编译需要一些时间,因此最好在开始时执行一次。
  2. 在OpenCL中开始时,最好将1个线程视为for循环的一次迭代。
  3. 要处理for循环的迭代,请在1,2或3维中使用clEnqueueNDRangeKernel(...)
  4. I always treat my OpenCL memory as 3D, since it is easier to convert 3D to 1D than it is 1D to 3D (for me at least). Please see my linked answer.