CUDA:每个线程计算的最佳像素数(灰度)

时间:2015-12-16 11:52:16

标签: image-processing cuda nvidia

我正在开发一个程序来转换灰度图像。我正在使用CImg库。我必须为每个像素读取3个值R-G-B,计算相应的灰度值并将灰色像素存储在输出图像上。我正在使用 NVIDIA GTX 480 。关于卡的一些细节:

  • 微架构:费米
  • 计算能力(版本): 2.0
  • 每SM的核心数(经纱尺寸): 32
  • 流式多处理器: 15
  • 每个多处理器的最大驻留warp数: 48
  • 每个多处理器的最大共享内存量: 48KB
  • 每个多处理器的最大驻留线程数: 1536
  • 每个多处理器的32位寄存器数: 32K

我使用的是带有256个线程块的方格。 该程序可以具有不同尺寸的输入图像(例如512x512像素,10000x10000像素)。我观察到增加分配给每个线程的像素数会增加性能,因此它比计算每个线程一个像素更好。问题是,如何确定静态分配给每个线程的像素数?计算每个可能的数字测试?我知道在GTX 480上,1536是每个多处理器的最大驻留线程数。我要考虑这个号码吗?以下是内核执行的代码。

for(i = ((gridDim.x + blockIdx.x) * blockDim.x) + threadIdx.x; i < width * height; i += (gridDim.x * blockDim.x)) {
    float grayPix = 0.0f;
    float r = static_cast< float >(inputImage[i]);
    float g = static_cast< float >(inputImage[(width * height) + i]);
    float b = static_cast< float >(inputImage[(2 * width * height) + i]);

    grayPix = ((0.3f * r) + (0.59f * g) + (0.11f * b));
    grayPix = (grayPix * 0.6f) + 0.5f;
    darkGrayImage[i] = static_cast< unsigned char >(grayPix);
}

1 个答案:

答案 0 :(得分:4)

  

问题是,如何确定静态分配给每个线程的像素数?计算每个可能数字的测试?

虽然您没有显示任何代码,但您已经提到了观察到的特征:

  

我观察到增加分配给每个线程的像素数会增加性能,

对于这些类型的工作负载来说,这实际上是一种相当常见的观察结果,并且可能在Fermi上比在较新的体系结构上更明显。在矩阵转置期间发生类似的观察。如果你写一个“天真”矩阵转置,每个线程转换一个元素,并将它与每个线程转换多个元素的讨论here的矩阵转置进行比较,你会发现,特别是在费米,每个线程的多个元素转置可以实现设备上大约可用的内存带宽,而每个线程的单元素转置不能。这最终与机器隐藏延迟的能力有关,并且代码能够暴露足够的工作以使机器隐藏延迟。理解基础行为有点牵扯,但幸运的是,优化目标非常简单。

当GPU等待先前发布的操作完成时,GPU会通过切换到大量可用工作来隐藏延迟。因此,如果我有大量的内存流量,那么对内存的单个请求会有很长的延迟。如果我在等待内存流量返回数据时机器可以执行其他工作(即使该工作会产生更多内存流量),那么机器可以使用该工作来保持忙碌并隐藏延迟。

为机器提供大量工作的方法首先要确保我们已经启用了最大数量的经线,这些经线可以适应机器的瞬时容量。这个数字计算相当简单,它是GPU上SM数量和每个SM上可以驻留的最大warp数的乘积。我们希望启动一个满足或超过此数字的内核,但超出此数量的其他warp / block不一定可帮助我们隐藏延迟。

一旦我们满足上述数字,我们希望尽可能多地将“工作”打包到每个线程中。实际上,对于您描述的问题和矩阵转置情况,在每个线程中包含尽可能多的工作意味着每个线程处理多个元素。

所以步骤很简单:

  1. 启动尽可能多的经线,因为机器可以即时处理
  2. 如果可能,将所有剩余的工作放在线程代码中。
  3. 我们来看一个简单的例子。假设我的GPU有2个SM,每个SM可以处理4个warp(128个线程)。请注意,这是核心数,而是“每个多处理器的最大常驻warp数”,如deviceQuery输出所示。

    我的目标是创建一个包含8个warp的网格,即总共256个线程(在至少2个线程块中,因此它们可以分配到2个SM中的每一个)并使这些warp通过处理多个元素来执行整个问题线。因此,如果我的整体问题空间总共是1024x1024个元素,那么理想情况下我希望每个线程处理1024 * 1024/256个元素。

    请注意,此方法为我们提供了优化方向。我们不一定要完全实现这个目标,以使机器饱和。可能的情况是,例如,每个线程只需要处理8个元素,以便允许机器完全隐藏延迟,并且通常会出现另一个限制因素,如下所述。

    遵循此方法将倾向于删除延迟作为内核性能的限制因素。使用分析器,您可以通过多种方式评估延迟是一个限制因素的程度,但一个相当简单的方法是捕获sm_efficiency metric,并可能在两者中比较该指标您已概述的案例(每个线程一个元素,每个线程多个元素)。我怀疑你会发现,对于你的代码,sm_efficiency指标表明每个线程情况下多个元素的效率更高,这表明延迟不是那种情况下的限制因素。

    一旦消除了延迟作为限制因素,您将倾向于遇到性能的另外两个机器限制因素之一:计算吞吐量和内存吞吐量(带宽)。在矩阵转置的情况下,一旦我们充分处理了延迟问题,那么内核往往以受内存带宽限制的速度运行。