【问题标题】:OpenCL - clEnqueueNDRangeKernel - output array becomes input arrayOpenCL - clEnqueueNDRangeKernel - 输出数组变成输入数组
【发布时间】:2022-04-17 02:02:48
【问题描述】:

我在二维数组上执行 5 点模板操作,直到在这个二维数组上计算收敛。所以我有多次迭代(直到收敛),对于每次迭代,我都调用clEnqueueNDRangeKernel 函数来计算二维输入数组的新值。

实际上,我操作一维数组,因为内核代码不支持二维(至少,我相信)。

我的问题是我不知道如何在输出和输入数组之间做做作。在计算迭代(模板操作)之后,我想将输出分配给下一次迭代的输入。

但我对如何实现这一点感到困惑。

在我的主循环中使用的函数下方:

while(!convergence)
    {
      step = step + 1;

      Compute_Stencil(command_queue, global_item_size, local_item_size, kernel, x0_mem_obj, x_mem_obj, r_mem_obj, x_input, r, size_x, size_y, &error) ;

      convergence = sqrt(error);

      if ((convergence<epsilon) || (step>maxStep)) break;
    }

其中x0_mem_obj 是与x_input 数组关联的缓冲区,x_mem_obj 与x_ouput 数组关联。

还有我感兴趣的 Compute_Stencil 函数:

void Compute_Stencil(cl_command_queue command_queue, size_t* global_item_size, size_t* local_item_size, cl_kernel kernel, cl_mem x0_mem_obj, cl_mem x_mem_obj, cl_mem r_mem_obj, double* x, double* r, int size_x, int size_y, double* error)
{

status = clEnqueueNDRangeKernel(command_queue, kernel, 2, NULL,
        global_item_size, local_item_size, 0, NULL, NULL);

 // Read the buffer back to the array
  if(clEnqueueReadBuffer(command_queue, x_mem_obj, CL_TRUE, 0,
        (size_x+2) * (size_y+2) * sizeof(double), x, 0, NULL, NULL) != CL_SUCCESS)
    fprintf(stderr,"Error in clEnqueueReadBuffer with x_mem_obj\n");


  if(clEnqueueReadBuffer(command_queue, r_mem_obj, CL_TRUE, 0,
        (size_x+2) * (size_y+2) * sizeof(double), r, 0, NULL, NULL) != CL_SUCCESS)
    fprintf(stderr,"Error in clEnqueueReadBuffer with r_mem_obj\n");


  status = clFlush(command_queue);
  if(status)
  {fprintf(stderr,"Failed to flush command Queue\n");
    exit(-1);}

  if(clEnqueueWriteBuffer(command_queue, x0_mem_obj, CL_TRUE, 0,
        (size_x+2) * (size_y+2) * sizeof(cl_double), x, 0, NULL, NULL) != CL_SUCCESS)
    fprintf(stderr,"Error in clEnqueueWriteuffer with x0_mem_obj\n");

 // Set new Argument - Outputs become Inputs
  status = clSetKernelArg(
      kernel,
      5,
      sizeof(cl_mem),
      (void*)&x0_mem_obj);

...

我认为这不是最好的方法,因为对于每次迭代,我必须将输出 x_mem_obj 缓冲区读取到 x_input(使用 clEnqueueReadBuffer)并将 x_input 写入 x0_mem_obj 缓冲区(使用 @987654334 @) 最后将 x0_mem_obj 缓冲区设置为 kernelArg(第 5 个参数):此缓冲区表示 main 中的输入 x0_mem_obj:

ret = clSetKernelArg(kernel, 5, sizeof(cl_mem), (void *)&x0_mem_obj);

我认为这不是好方法,因为性能很差(我认为 Read 和 Write Buffer 操作会花费很多时间)。

我尽量不在Compute_Stencil函数中使用ReadBuffer和WriteBuffer,直接将输出缓冲区x_mem_obj放在下一次调用的第5个参数中:

 status = clEnqueueNDRangeKernel(command_queue, kernel, 2, NULL,
        global_item_size, local_item_size, 0, NULL, NULL);

status = clFlush(command_queue);

// Set new Argument - Outputs become Inputs
  status = clSetKernelArg(
      kernel,
      5,
      sizeof(cl_mem),
      (void*)&x_mem_obj);

但结果无效。

在调用 NDRangeKernel 之后,如何简单地将输出数组转移到输入数组,以便下次调用 NDRangeKernel?

更新 1

@doqtor,感谢您的回答,但我必须指定,在计算新值之后(即在调用 NDRangeKernel 之后),我需要将新的计算值分配给输入,但我认为我没有t 需要用输出数组替换输入数组:输出缓冲区将被根据输入缓冲区值计算的新值系统地覆盖。

在我的内核代码中,我有以下参数:

__kernel void kernelHeat2D(const double diagx, const double diagy,
                                 const double weightx, const double weighty,
                                 const int size_x,
                                 __global double* tab_current,
                                 __global double* tab_new,
                                 __global double* r)

其中tab_new 是输出数组,tab_current 是输入数组。 tab_current 是第 6 个参数(在 clSetKernelArg 中编号为 5)。

这就是为什么在 NDRangeKernel 调用之后,我认为我只需要使用:

// Set new Argument - Outputs become Inputs
  status = clSetKernelArg(
      kernel,
      5,
      sizeof(cl_mem),
      (void*)&x_mem_obj);

更新 2

Update 1 中的上述方法不起作用:我在数组“r”(我的代码中的缓冲区为r_mem_obj)中得到了执行随机差值。该数组允许计算收敛性,因此每次执行时我得到不同数量的步骤。

要工作,我必须明确地放入主循环:

while (!convergence) {

clEnqueueNDRangeKernel();

// Read output buffer and put it into xOutput
clEnqueueReadBuffer( x_mem_obj, xOutput);

// Read error buffer and put it into r
clEnqueueReadBuffer( r_mem_obj, r);

// Write output array to input buffer
clEnqueueWriteBuffer( x0_mem_obj, xOutput)

// put input buffer into input argument for next call of NDRangeKernel
status = clSetKernelArg(
      kernel,
      5,
      sizeof(cl_mem),
       (void*)&x0_mem_obj);
}

我想避免使用ReadBuffer 和WriteBuffer(强制将xOutput 设置为输入x0_mem_obj 缓冲区),因为从时间执行的角度来看,它的性能很差。

【问题讨论】:

    标签: c buffer opencl


    【解决方案1】:

    问题似乎是您将输出设置为仅输入,然后您拥有与输入和输出相同的缓冲区。您需要交换缓冲区:

    buffer1 = create buffer 1
    buffer2 = create buffer 2
    
    clEnqueueWriteBuffer(..., buffer1, ...);
    clEnqueueWriteBuffer(..., buffer2, ...);
    
    cl_mem *ptrInput = &buffer1;
    cl_mem *ptrOutput = &buffer2;
    
    for(..)
    {
        clSetKernelArg(..., inputIdx, ptrInput, ...);
        clSetKernelArg(..., outputIdx, ptrOutout, ...);
        clEnqueueNDRangeKernel(...);
    
        // swap buffers
        cl_mem *ptrTpm = ptrInput;
        ptrInput = ptrOutput;
        ptrOuput = ptrTmp;
    }
    
    // ...
    // Read results data back
    clEnqueueReadBuffer(..., ptrInput, ...); // read from ptrInput because we did extra swap
    

    【讨论】:

    • 我知道我必须将输出缓冲区设置为输入缓冲区,但我不明白当您执行 ptrOutput = ptrTmp 时,调用 NDRangeKernel 后输出缓冲区不会自动覆盖?看来我只需要把新的输入放在这个调用之前。
    • 我将 double 更改为 cl_mem 以防让您感到困惑。还要记住clSetKernelArg 需要一个指向缓冲区的指针,而不是整个缓冲区。想想如何在不复制 C/C++ 中的数据的情况下进行缓冲区交换,这与这里的方法类似。
    • 好吧,也许我明白了:我以为我只需要为下一次调用 NDRangeKernel 将输出缓冲区设置为输入缓冲区:这样,我在调用内核代码之前放置了相同的缓冲区。但是在我的内核代码中,我通过以下方式计算下一个值(输出值): int iy = get_global_id(0)+1; int ix = get_global_id(1)+1; output_array[iy(size_x+2)+ix] =function(input_array);
    • 所以工作项线程之间存在冲突。最后,解决方案是,除了将输出缓冲区设置为输入缓冲区之外,还要在调用 NDRangeKernel 之前将输入缓冲区设置为输出一个,以避免覆盖值的冲突,这就是我需要交换的原因:这个解释是对吗?
    • 有人可以回答吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-08
    • 1970-01-01
    • 1970-01-01
    • 2012-10-09
    相关资源
    最近更新 更多