在OpenCL中写入全局内存

时间:2014-09-26 18:09:52

标签: memory opencl

我试图优化最初用Fortran编写的一些代码。

该算法涉及在几次迭代中对大型阵列(约2700万个单元)进行操作。可以在一次迭代期间独立评估每个单元。但是,迭代无法并行化,因为在t + 1处完成的计算取决于在t处完成的计算结果。

粗略的,简化的非并行伪代码示例:

for (t=0; t<tmax; t++)
{
A = A + B;

B  = B + A /2;
}

其中A和B是大数组。

目前,我已经通过在主机C ++代码中的循环中调用EnqueueNDRangeKernel来实现这一点。因为我需要前一次迭代的结果,所以每次都写入全局内存。

每次迭代执行2700万次全局内存写入会导致我的性能下降。我有两个内核版本,我正在玩;与Fortran相比,版本1快了约2.5倍;版本2快4倍。

我试图摆弄算法,以及使用指针(版本2)。

我的问题如下:有没有办法避免这种全局内存写入阻塞点?

谢谢!


请求的代码:

用C ++调用:

NDRange global(nxp1*ny*nz);
NDRange local(nz);

    for (w=0; w<100; w++)
    {
       queue.enqueueNDRangeKernel(kernA, NullRange, global, local);
        queue.enqueueBarrierWithWaitList();
       queue.enqueueNDRangeKernel(kernB, NullRange, global, local);
    }

queue.finish();

内核:

__kernel void kernA(__global double *A, __global double *B)
    {
    int i = get_global_id(0);

    double A_l;
    A_l = A[i];
    double B_l;
    B_l = B[i];

    A_l = A_l + B_l;

    A[i] = A_l; //if this line is removed, everything goes much faster.

    }

   __kernel void kernB(__global double *A, __global double *B)
    {
    int i = get_global_id(0);

    double A_l;
    A_l = A[i];
    double B_l;
    B_l = B[i];

    B_l = B_l + A_l/2;

    B[i] = B_l; //if this line is removed, everything goes much faster.
    }

为了澄清算法,我简化了内核代码。但我的想法是我基于B更新A;然后我根据A更新B.重复几次迭代。

2 个答案:

答案 0 :(得分:0)

没有办法完全避免全局写入问题。您正在编写一次值,并且您的速度受硬件限制。您可以减少全局读取的数量,只要您不介意一次计算多个步骤。这仍然保存了每一步。

__kernel void myKernel(__global double *A, __global double *B, __global uint outDataMultiple)              
{                                                                                      
    const uint gid = get_global_id(0);
    const uint inDataSize = get_global_size(0);

    double2 nextValue;
    nextValue.x = A[gid];
    nextValue.y = B[gid];
    for(uint i=0; i<outDataMultiple; i++){
        nextValue.x = nextValue.x + nextValue.y;
        nextValue.y = nextValue.y + nextValue.x /2;
        A[gid+i+1] = nextValue.x;
        B[gid+i+1] = nextValue.y;
    }
}

使用上面的内核,工作项将负责单个单元的多次迭代。你需要为outDataMultiple分配更多的内存,内核将填写剩下的内存。全局工作项计数确定初始输入的大小。 outDataMultiple仅受全局内存分配的限制,并且可能是您在每次迭代时所做的数学复杂性。

所需的全球总内存: 27M * sizeof(double2)*(1 + outDataMultiple)

__kernel void myKernel(__global double2 *data, __global uint outDataMultiple)              
{                                                                                      
    const uint gid = get_global_id(0);
    const uint inDataSize = get_global_size(0);

    double2 nextValue = data[gid];
    for(uint i=0; i<outDataMultiple; i++){
        nextValue.x = nextValue.x + nextValue.y;
        nextValue.y = nextValue.y + nextValue.x /2;
        data[gid+i+1] = nextValue;
    }
}

只要您可以交错A和B向量,就可以使用相同内核的double2版本。这将结合读取和写入来保证8字节块,并可能提高性能。

答案 1 :(得分:0)

减少OpenCL设备从全局内存中获取的时间的一种简单方法是批量缓存全局内存到本地内存,在本地内存上运行,然后批量将本地内存写入全局内存。

本地内存与线程内存具有基本相同的延迟,可以从块中的全局内存中读取。本地内存可以在主机上声明并传递给内核(参见下面的示例)或在内核中分配并使用(参见下面列出的AMD优化指南中的示例)。例如:

__kernel void kernA(__global double *A, 
                    __global double *B, 
                    __local double *BufferA,
                    __local double *BufferB)
    {
    BufferA[get_local_id(0)] = A[get_global_id(0)];
    BufferB[get_local_id(0)] = B[get_global_id(0)];
    mem_fence(CLK_LOCAL_MEM_FENCE);

    double tmp = BufferA[get_local_id(0)] + BufferB[get_local_id(0)];

    A[get_global_id(0)] = BufferA[get_local_id(0)];
    mem_fence(CLK_GLOBAL_MEM_FENCE);
    }

还有其他事情可以做,包括:

相关问题