共享内存Cuda,用于将RGB图像转换为灰度

时间:2015-11-09 13:57:22

标签: c++ image memory cuda

我是Cuda编程的新手,我有一个将RGB图像转换为Greyscale的代码。已经提供了用于读取像素的RGB值并将它们转换为GreyScale的算法。 并行化代码使我的速度提高了大约40-50倍。我想进一步优化它以实现大约100倍的加速。为此,我希望使用共享内存访问的速度比全局内存访问快。我已经浏览了不同的在线资源,并对共享内存访问有基本的了解。但在我的代码中,我遇到了解如何实现共享内存的问题,读取RGB值和转换为灰度的代码

    for ( int y = 0; y < height; y++ ) {
      for ( int x = 0; x < width; x++ ) {
        float grayPix = 0.0f;
        float r = static_cast< float >(inputImage[(y * width) + x]);
        float g = static_cast< float >(inputImage[(width * height) + (y * width) + x]);
        float b = static_cast< float >(inputImage[(2 * width * height) + (y * width) + x]);

        grayPix = ((0.3f * r) + (0.59f * g) + (0.11f * b));
        grayPix = (grayPix * 0.6f) + 0.5f;

        darkGrayImage[(y * width) + x] = static_cast< unsigned char >(grayPix);
        }
     }

输入图像char *,我们使用CImg库来操作图像

CImg< unsigned char > inputImage = CImg< unsigned char >(argv[1]);

用户在运行代码时将图像路径作为参数传递给

这是我对它的Cuda实施

unsigned int y = (blockIdx.x * blockDim.x) + threadIdx.x;
unsigned int x = (blockIdx.y * blockDim.y) + threadIdx.y;
float grayPix = 0.0f;
float r = static_cast< float >(inputImage[(y * height) + x]);
float g = static_cast< float >(inputImage[(width * height) + (y * height) + x]);
float b = static_cast< float >(inputImage[(2 * width * height) + (y * height) + x]);    
grayPix = ((0.3f * r) + (0.59f * g) + (0.11f * b));
grayPix = (grayPix * 0.6f) + 0.5f;

darkGrayImage[(y * height) + x] = static_cast< unsigned char >(grayPix);

网格并阻止并调用代码

    dim3 gridSize(width/16,height/16);
    dim3 blockSize(16,16);
    greyScale<<< gridSize, blockSize >>>(width,height,d_in, d_out);

其中width和height是输入图像的宽度和高度。我试过块大小为(32,32),但它减慢了代码而不是加速它

现在我想添加共享内存但问题是输入变量InputImage的访问是非线性的,所以我要添加到共享内存中的值是什么 我试过像

这样的东西
 unsigned int y = (blockIdx.x * blockDim.x) + threadIdx.x;
 unsigned int x = (blockIdx.y * blockDim.y) + threadIdx.y;
 extern __shared__ int s[];
 s[x]=inputImage[x];
 __syncthreads();

然后在实现中用s替换inputImage但是输出错误(全黑图像) 你能帮助我在这里了解如何实现共享内存,即使它是可能的和有用的,有没有办法让我以更加合并的方式进行访问?

任何帮助都会感激不尽

1 个答案:

答案 0 :(得分:2)

由于以下几个原因,这不起作用:

 unsigned int x = (blockIdx.y * blockDim.y) + threadIdx.y;
 extern __shared__ int s[];
 s[x]=inputImage[x];

一个原因是我们不能使用全局索引(x)作为共享内存索引,除非数据集足够小以适应共享内存。对于尺寸相当大的图像,您无法将整个图像放入共享内存的单个实例中。此外,您只使用二维数据集的一维索引(x),因此这可能没有意义。

这表明对如何在程序中使用共享内存的普遍缺乏了解。然而,我们可以观察到,对于正确编写的RGB-&gt;灰度代码,共享内存使用不太可能提供任何好处,而不是试图对其进行排序。

共享内存带宽优势(当您说“速度更快”时,您指的是这一点)在数据重用时非常有用。 RGB->灰度代码不需要重复使用任何数据。您只需从全局内存中加载一次R,G,B数量,并将计算出的灰度数量恰好存储在全局内存中一次。暂时将某些数据移动到共享内存不会加快速度。您仍然需要执行全局加载和全局存储,并且对于正确编写的代码,这应该是所有必要的。

但是在您的问题中,您已经建议了一条可能的改进路径:合并访问。如果您要对发布的代码进行概要分析,您会发现完全未合并的访问模式。为了实现良好的合并,我们希望复合索引计算具有threadIdx.x变量不会乘以任何内容的属性:

unsigned int y = (blockIdx.x * blockDim.x) + threadIdx.x;
unsigned int x = (blockIdx.y * blockDim.y) + threadIdx.y;
float grayPix = 0.0f;
float r = static_cast< float >(inputImage[(y * height) + x]);
                                           ^
                                           |
                                           y depends on threadIdx.x

但在您的情况下,您的索引计算会将threadIdx.x乘以height。这将导致非合并访问。 warp中的相邻线程将具有不同的threadIdx.x,并且我们希望warp中相邻线程的索引计算导致内存中的相邻位置,以实现良好的合并访问。如果将threadIdx.x乘以任何东西,则无法实现此目的。

这个问题的解决方案非常简单。您应该使用与您显示的非CUDA代码几乎完全相同的内核代码,并使用xy的适当定义:

    unsigned int x = (blockIdx.x * blockDim.x) + threadIdx.x;
    unsigned int y = (blockIdx.y * blockDim.y) + threadIdx.y;
    if ((x < width) && (y < height)){ 
      float grayPix = 0.0f;
      float r = static_cast< float >(inputImage[(y * width) + x]);
      float g = static_cast< float >(inputImage[(width * height) + (y * width) + x]);
      float b = static_cast< float >(inputImage[(2 * width * height) + (y * width) + x]);

      grayPix = ((0.3f * r) + (0.59f * g) + (0.11f * b));
      grayPix = (grayPix * 0.6f) + 0.5f;

      darkGrayImage[(y * width) + x] = static_cast< unsigned char >(grayPix);
      }

当然,这不是一个完整的代码。你没有显示完整的代码,所以如果你回答“我试过这个但它不起作用”,我不太可能帮助你,因为我不知道你实际运行的代码是什么。但是:

  1. 共享内存不是采用此算法的正确方法
  2. 由于我指出
  3. 的原因,您在发布的代码中无疑会出现合并问题
  4. 合并修复应遵循我概述的路径
  5. 通过合并修复,您的表现会有所改善。
  6. 请注意,“它不起作用”的响应意味着您确实要求调试帮助,而不是概念性解释,在这种情况下,您supposed to提供MCVE。你所展示的不是MCVE。优选地,您的MCVE不应该依赖于像CImg这样的外部库,这意味着您需要努力创建一个独立测试,但是要证明您遇到的问题。

    此外,我建议您在使用CUDA代码时遇到问题,使用proper CUDA error checking以及使用cuda-memcheck运行代码。

    (正确的CUDA错误检查会发现您尝试使用共享内存时出现问题,例如,由于共享内存中的越界索引。)