OpenCL - clEnqueueNDRangeKernel - 输出数组变为输入数组
OpenCL - clEnqueueNDRangeKernel - output array becomes input array
我在二维数组上执行 5 点模板操作,直到在该二维数组上计算收敛。所以我有多次迭代(直到收敛)并且对于每次迭代,我调用 clEnqueueNDRangeKernel
函数来计算 2D 输入数组的新值。
实际上,我操作一维数组,因为内核代码不支持二维(至少,我相信)。
我的问题是我不知道如何在输出数组和输入数组之间做做作。在计算迭代(模板操作)后,我想将输出分配给下一次迭代的输入。
但我对如何实现这一点感到困惑。
在我的主循环中使用的函数下面:
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
缓冲区(使用 clEnqueueWWriteBuffer
),最后将 x0_mem_obj
缓冲区设置为 kernelArg(第 5 个参数):此缓冲区表示 main 中的输入 x0_mem_obj
:
ret = clSetKernelArg(kernel, 5, sizeof(cl_mem), (void *)&x0_mem_obj);
我认为这不是一个好方法,因为性能很差(我认为读取和写入缓冲区操作会花费很多时间)。
我尽量不在 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 之后),我需要将新的计算值分配给输入,但我想我不需要用输出数组替换输入数组:输出缓冲区将被根据输入缓冲区值计算的新值系统地覆盖。
在我的内核代码中,我有以下参数:
__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
更新 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
缓冲区),因为它从时间执行点开始表现不佳观点。
问题似乎是您将输出设置为仅输入,然后您拥有与输入和输出相同的缓冲区。您需要交换缓冲区:
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
我在二维数组上执行 5 点模板操作,直到在该二维数组上计算收敛。所以我有多次迭代(直到收敛)并且对于每次迭代,我调用 clEnqueueNDRangeKernel
函数来计算 2D 输入数组的新值。
实际上,我操作一维数组,因为内核代码不支持二维(至少,我相信)。
我的问题是我不知道如何在输出数组和输入数组之间做做作。在计算迭代(模板操作)后,我想将输出分配给下一次迭代的输入。
但我对如何实现这一点感到困惑。
在我的主循环中使用的函数下面:
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
缓冲区(使用 clEnqueueWWriteBuffer
),最后将 x0_mem_obj
缓冲区设置为 kernelArg(第 5 个参数):此缓冲区表示 main 中的输入 x0_mem_obj
:
ret = clSetKernelArg(kernel, 5, sizeof(cl_mem), (void *)&x0_mem_obj);
我认为这不是一个好方法,因为性能很差(我认为读取和写入缓冲区操作会花费很多时间)。
我尽量不在 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 之后),我需要将新的计算值分配给输入,但我想我不需要用输出数组替换输入数组:输出缓冲区将被根据输入缓冲区值计算的新值系统地覆盖。
在我的内核代码中,我有以下参数:
__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
更新 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
缓冲区),因为它从时间执行点开始表现不佳观点。
问题似乎是您将输出设置为仅输入,然后您拥有与输入和输出相同的缓冲区。您需要交换缓冲区:
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