与CUDA上的GPU上的简单算法并行

Paralleling a simple algorithm on GPU with CUDA

本文关键字:简单 算法 并行 CUDA 上的 GPU      更新时间:2023-10-16

i具有一个CUDA函数,可以计算GPU上的局部二进制模式。基本上LBP是对图像像素的计算,其中任何给定像素(I,J)的值取决于它的8个邻居的强度。

到目前为止很好,代码如下:

//The kernel
__global__ void LBP(unsigned char *in, unsigned char *out, const int w, const int h)
{
    const unsigned int i = (blockIdx.x * blockDim.x) + threadIdx.x;
    //Don't do edges!
    if(
             i < w              //first row
        ||   i >= (w * (h - 1)) // last row
        || !(i % w)             // first column
        ||  (i % w + 1 == w)    // last column
    )
    {
        out[i] = 0;
        return;
    }
    unsigned char
        code = 0,
        center = in[i];
    code |= (in[i-w-1] > center) << 7;
    code |= (in[i-w  ] > center) << 6;
    code |= (in[i-w+1] > center) << 5;
    code |= (in[i  +1] > center) << 4;
    code |= (in[i+w+1] > center) << 3;
    code |= (in[i+w  ] > center) << 2;
    code |= (in[i+w-1] > center) << 1;
    code |= (in[i  -1] > center) << 0;
    out[i] = code;
}
// A proxi function
void DoLBP(unsigned char *in, unsigned char *out, const int w, const int h)
{
    const int
        sz = w * h * sizeof(unsigned char);
    unsigned char
        *in_gpu,
        *out_gpu;
    cudaMalloc((void**)&in_gpu,  sz);
    cudaMalloc((void**)&out_gpu, sz);
    cudaMemcpy(in_gpu,  in,  sz, cudaMemcpyHostToDevice);
    cudaMemcpy(out_gpu, out, sz, cudaMemcpyHostToDevice);
    dim3 threadsPerBlock(1024); //Max
    dim3 numBlocks(w*h/threadsPerBlock.x + 1);
    LBP<<<numBlocks,threadsPerBlock>>>(in_gpu, out_gpu, w, h);
    cudaMemcpy(out, out_gpu, sz, cudaMemcpyDeviceToHost);
    cudaFree(in_gpu);
    cudaFree(out_gpu);
}
//The caller
int main()
{
    printf("Startingn");
    const int
        w = 4000,
        h = 2000;
    unsigned char
        in[w*h],
        out[w*h];
    // Fill [in] with some data 
    DoLBP(in, out, w, h);

    // Use [out] data
    return 0;
}

这些图像作为 * unsigned char *s( array = [[row 1] [row 2] [row 3] ... [row n]])的单维数组传递给GPU(从OpenCV的垫子中提取)

问题

此代码在相对较小的图像中正常工作,它返回带有正确值的输出数组,但是当图像大小增长时,输出数组全部为零!

我的怀疑是,图像数据溢出了一些GPU缓冲区或类似的东西。

我也不清楚 numberfblocks threadspersperblock part的工作!如果你们中的任何一个可以对此提供一些基本的见解,那将不胜感激。

(我在Cuda中像1天大,所以可能有太多方法可以改善此代码段!)

  1. 我建议在您的代码中添加适当的CUDA错误检查。我相信您的内核正在访问和失败。
  2. 使用cuda-memcheck运行代码,因为它将有助于确定内核为何失败。
  3. 这些是在堆栈上进行的相当大的分配:

    const int
      w = 4000,
      h = 2000;
    unsigned char
      in[w*h],
      out[w*h];
    

    每个大约8MB。这可能是一个问题;它可能依赖于系统。通常最好通过动态分配进行大规模分配,例如malloc。在我的特定系统上,由于这些大型堆栈变量未正确分配,因此我得到了SEG错误。

  4. 您的内核缺少适当的"线程检查"。起初,我以为您对此做得很好:

    if(
         i < w              //first row
      ||   i >= (w * (h - 1)) // last row
      || !(i % w)             // first column
      ||  (i % w + 1 == w)    // last column
    )
    

    但这是一个问题:

    out[i] = 0;
    return;
    

    如果您评论out[i] = 0;行,您将有更好的运气。另外,如果您不喜欢发表评论,则可以这样做:

    if (i < (w*h)) out[i] = 0;
    

    问题是您的网格启动参数必定会创建"额外线程":

    dim3 threadsPerBlock(1024); //Max
    dim3 numBlocks(w*h/threadsPerBlock.x + 1);
    

    如果您有适当的线程检查(几乎可以做...),那不是问题。但是您不能让这些额外的线程写入无效的位置。

要解释每个块螺纹和通过算术工作的块数量可能是有用的。CUDA内核发射具有关联的网格。网格只是与内核启动相关联的所有线程。线程将分为块。因此,网格等于启动的块数量,每块线程。那是多少?这条线说您每块要求1024个线程:

    dim3 threadsPerBlock(1024); //Max

您要启动的块数量由:

给出
    dim3 numBlocks(w*h/threadsPerBlock.x + 1);

算术是:

    (w=4000)*(h=2000)/1024 = 7812.5 = 7812   (note this is an *integer* divide)

然后我们添加1。因此您启动了7813个块。那是几个线程?

    (7813 blocks)*(1024 threads per block) = 8000512 threads

,但是您只需要(只需要)8000000螺纹(= w * h),因此您需要进行线程检查即可防止额外的512线程尝试访问out[i]。但是您的线程检查在这方面被打破了。

作为最后的注意,让我更快地运行此代码的最明显方法是通过共享内存利用相邻操作中的数据重新使用。但请先让您的代码正常工作。