如何在具有多个用户定义函数的OpenCL内核中共享全局变量(数组)

时间:2016-04-22 15:23:49

标签: arrays kernel global-variables opencl local-variables

我的OpenCL内核出了问题。我正在努力做Runge-Kutta 4整合。我已经在OpenGL计算着色器中实现了它,它可以工作,现在我想在OpenCL中实现它。

我认为我的问题与不知道如何在我的所有函数调用中正确共享全局数组的单个实例有关,因为现在我必须将指针发送到数组作为每个函数的参数调用,在我看来,这实际上在这些函数中创建了一个本地副本,因为我当前的实现适用于小数据集但不适用于大数据集(它们抛出CL_OUT_OF_RESOURCES)。

在我的计算着色器中,我声明了所有的全局数组:

layout(std430, binding=0) buffer pblock { coherent volatile restrict vec4 mcPosition[]; };

layout(std430, binding=1) buffer vblock { coherent volatile restrict vec4 mcVelocity[]; };

我可以在我的功能中使用它们:

vec4 calculateAcceleration(int numPoints, int step, ...) {...}

void rk4Step(int numPoints, int index, float timeStepToUse, ...) {...}

void calculateError(int index) {...}

但是在OpenCL实现中,我知道如何做的唯一方法是这样的(非常简洁的例子):

void rk4Step(
    const __constant int* numPoints,
    const int index,
    const float timeStepToUse,
    const bool calculateHalfTimeStep,
    const __constant float* squaredSofteningFactor,
    const __constant float* gravitationalConstant,
    __global float4* kvel,
    __global float4* dydx,
    __global float4* kpos,
    __global float4* mcPositionHalf,
    __global float4* mcVelocityHalf,
    __global float4* mcPositionFull,
    __global float4* mcVelocityFull
    )
{
    ...

    // Actual time step
    if(!calculateHalfTimeStep)
    {
        mcVelocityFull[index] += (kvel[index] + (2.0f*kvel[index+numPoints[0]]) + (2.0f*kvel[index+numPoints[0]*2]) + kvel[index+numPoints[0]*3]) * (1.0f/6.0f);
        mcPositionFull[index] += (kpos[index] + (2.0f*kpos[index+numPoints[0]]) + (2.0f*kpos[index+numPoints[0]*2]) + kpos[index+numPoints[0]*3]) * (timeStepToUse/6.0f);
    }
    else
    {
        mcVelocityHalf[index] += (kvel[index] + (2.0f*kvel[index+numPoints[0]]) + (2.0f*kvel[index+numPoints[0]*2]) + kvel[index+numPoints[0]*3]) * (1.0f/6.0f);
        mcPositionHalf[index] += (kpos[index] + (2.0f*kpos[index+numPoints[0]]) + (2.0f*kpos[index+numPoints[0]*2]) + kpos[index+numPoints[0]*3]) * (timeStepToUse/6.0f);
    }
}

void calculateError(const int index, __global float4* scale)
{
    float partialError = 0.0f;
    partialError = fmax(partialError, fabs(deltaPos[index].x / scale[index].x));
}

// Adaptive step 4th order Runge-Kutta
__kernel
void main( const __constant float* timeStep, const __constant float* accuracy, const __constant int* maxSteps,
    __global float4* mcPosition, __global float4* mcVelocity, __global float4* scale)
{
    // Scaling used to monitor accuracy
    scale[index] = calculateAcceleration(bi, index, numPoints, 1, false,
        squaredSofteningFactor, gravitationalConstant,
        mcPositionHalf, mcPositionFull, kvel);

    scale[index] = fabs(mcVelocity[index]) + fabs(scale[index] * timeStep[0]);

    for(int step=1; step<=maxSteps[0]; ++step)
    {
        // Take two half steps
        rk4Step(numPoints, index, timeStep[0], true,
            squaredSofteningFactor, gravitationalConstant,
            mcPosition, mcVelocity);
        rk4Step(numPoints, index, timeStep[0], true,
            squaredSofteningFactor, gravitationalConstant,
            mcPosition, mcVelocity);

        // Take one full step
        timeStep[0] *= 2.0f;
        rk4Step(numPoints, index, timeStep[0], false,
            squaredSofteningFactor, gravitationalConstant,
            mcPosition, mcVelocity);

        // Evaluate accuracy
        calculateError(index, accuracy, scale, deltaPos);
    }
}

您可以注意到,不同之处在于,在计算着色器版本中,我可以在文件顶部声明共享的全局数组,并在我的任何一个函数中使用它们。

但是在OpenCL内核版本中,我必须将这些数组作为每个函数调用的参数传递,对于大型数据集,这会给我一个CL_OUT_OF_RESOURCES错误。

我认为我的问题与以下事实有关:即使我声明数组是全局的,每个函数调用都会尝试制作数组的本地副本,但也许我错了。我通过阅读文档来假设这一点,这个问题指出了同样的事情:

How many copies of a global variable declared inside an opencl kernel function is maintained in the global address space

所以我的问题是: 如何在用户定义的函数和我的OpenCL内核之间真正共享全局数组?

1 个答案:

答案 0 :(得分:1)

您提到的数组作为指针传递,没有理由期望整个数组的本地副本,还有__constant参数可以停止写入,并且副本也是__constant是只读的。无本地拷贝的主要原因可能是没有堆栈的gpu-opencl实现。人们编写假堆栈来实现虚假递归,但即使这样也不能大于主机代码中定义的大小。

你什么时候得到“CL_OUT_OF_RESOURCES”?更改__constant缓冲区大小或__global大小后?通常__constant每个GPU只有50-100 kB,而__global可以大到每个缓冲区每个gpu的1/4视频内存。偶数个__常量参数是有限的。您可以将多个常量数组连接成单个常量数组以消除它。查询常量和全局的常量内存限制。使用CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE从clGetDeviceInfo开始。

其他情况:

  • 堆碎片---&gt;没有大阵容。只有较小的可以分配为缓冲区。您是否正在发送使用所有vram(或常量vram)的并发内核?

  • 本地工作组的大小比设备大(例如:amd在gpu上有256,nvidia有1024)(至少它是全局大小的分隔符)

  • 标量寄存器太多,每个线程或每个smx / cu的矢量寄存器太多。

测试:

  • 每组有1024个线程。
  • 函数中至少有7个float4变量。
  • 每个float4是16个字节。
  • 如果每个函数都使用这些单个变量(从任何来源读取),
  • 每个smx需要112 kB,这比它更多(48 kB)
  • 每个线程只需要浮点数为112个字节。您也使用标量变量。您可以使用分析器查看它。

帮助:

  • 您可以更改/重新排序内核和函数中的内容,以便随时需要更少的寄存器。仅在使用之前声明某些内容。不是一开始。您还可以在完成其工作后重新使用寄存器(例如使用v1然后使用v1而不是v2,v3,再次使用v1,v2,v4 ......)。
  • 减少本地工作组的大小,因此每个sm​​x的线程数减少意味着每个smx的寄存器使用量减少。即使每个线程使用也很重要,但仅限于性能。
  • 有时低至32(或64) - 本地工作组大小可能是有利的,尽管一半的核心空闲,以获得更多的每个线程的内存空间。
  • 内联函数也会增加套准压力。也许你应该降低展开和内联的水平,然后再试一次。
  • 从rk4Step参数中删除const(非__constant)关键字。也许这些是在每个线程的__constant内存空间中预先分配的(不是不可能的)