【问题标题】:CUDA "convolution" as slow as OpenMP versionCUDA“卷积”与 OpenMP 版本一样慢
【发布时间】:2013-04-19 21:33:43
【问题描述】:

我正在尝试将 featWidth * featHeight * 31 立方体与另一个 modelWidth * modelHeight * 31 立方体“卷积”。问题是这个内核非常慢(嗯,我设法比顺序 CPU 代码快,但与 OpenMP 版本一样慢)。我正在使用 Quadro FX 1800(是的,64 个 CUDA 核心......)。

__constant__ float d_model[31*22*22];
#define IMUL(a,b) ( __mul24((a), (b)) )
#define IMAD(a,b,c) ( __mul24((a), (b)) + (c) )
__global__ void dMatch(float *score, const int featWidth, const int featHeight, const int modelWidth, const int modelHeight, const int scoreWidth, const int scoreHeight)
{
  const int x = IMAD(blockIdx.x, blockDim.x, threadIdx.x);
  const int y = IMAD(blockIdx.y, blockDim.y, threadIdx.y);
  if(x < scoreWidth && y < scoreHeight)
  {
   const int scoreIdx = IMAD(x, scoreHeight, y);
   score[scoreIdx] = 0.f;
   const int baseFeatIdx = IMUL(x,scoreHeight) + IMAD(modelHeight-1, x, y);
   for(int z = 0; z < 31; ++z)
   {
     // Index positionning
     int featIdx =  IMAD(z, IMUL(featWidth,featHeight), baseFeatIdx);
     int modelIdx = IMUL(z, IMUL(modelWidth,modelHeight));

     float value = 0.f;

     // filter
     for(int xx=0; xx<modelWidth; xx++)
     {
       const int xxmodelIdx = IMAD(xx, modelHeight, modelIdx);
       const int xxfeatIdx = IMAD(xx, featHeight, featIdx);
       for(int yy=0; yy<modelHeight; yy++)
       {
         value += d_model[xxmodelIdx+yy] * tex1Dfetch(texFeatures,xxfeatIdx+yy);
       }
     }
     score[scoreIdx] += value;
  }
 }
}

无论如何,我使用块中的8*8 线程和(scoreWidth/8)*(scoreHeight/8) 的网格大小启动这个内核(scoreWidth 和 scoreHeight 是生成的矩阵大小)。 我想知道你是否知道我的代码出了什么问题或者什么地方比较慢。

编辑:

一个更快的版本(480 毫秒的进程下降了 150 毫秒!)感谢 tera:

__global__ void dMatch(float *score, const int featWidth, const int featHeight, const int modelWidth, const int modelHeight, const int scoreWidth, const int scoreHeight)
{
    const int y = IMUL(4,IMAD(blockIdx.x, blockDim.x, threadIdx.x));
    const int x = IMAD(blockIdx.y, blockDim.y, threadIdx.y);
    if(x < scoreWidth && y < scoreHeight)
    {
    const int scoreIdx = IMAD(x, scoreHeight, y);
    const int baseFeatIdx = IMUL(x,scoreHeight) + IMAD(modelHeight-1, x, y);
    float value=0.f, value1 = 0.f, value2 = 0.f, value3 = 0.f;
    float feat,feat1,feat2,feat3;

    // Index positionning
    int featIdx =  0;
    int modelIdx = 0;
    int xxmodelIdx;
    int xxfeatIdx; 
    float val;
    for(int z = 0; z < 31; ++z)
    {
        featIdx = IMAD(z,IMUL(featWidth,featHeight),baseFeatIdx);
        modelIdx = IMUL(z,IMUL(modelWidth,modelHeight));

        // filter
        for(int xx=0; xx<modelWidth; xx++)
        {
            xxmodelIdx  = IMAD(xx, modelHeight, modelIdx);
            xxfeatIdx = IMAD(xx, featHeight, featIdx);
            feat=tex1Dfetch(texFeatures,xxfeatIdx+0);
            feat1=tex1Dfetch(texFeatures,xxfeatIdx+1);
            feat2=tex1Dfetch(texFeatures,xxfeatIdx+2);
            feat3=tex1Dfetch(texFeatures,xxfeatIdx+3);
            for(int yy=0; yy<modelHeight; yy++)
            {
                val = d_model[xxmodelIdx+yy];
                value += val * feat;
                value1 += val * feat1;
                value2 += val * feat2;
                value3 += val * feat3;
                feat = feat1;
                feat1 = feat2;
                feat2 = feat3;
                feat3 = tex1Dfetch(texFeatures,xxfeatIdx+yy+4);
            }
        }
    }
    score[scoreIdx] = value;
    if(y+1 < scoreHeight)
        score[scoreIdx+1] = value1;
    if(y+2 < scoreHeight)
        score[scoreIdx+2] = value2;
    if(y+3 < scoreHeight)
        score[scoreIdx+3] = value3;
}

用这个dim3 threads(16,16); dim3 grid(divup(scoreHeight,64), divup(scoreWidth,16));启动。

【问题讨论】:

  • 一些快速思考:使用绑定到 3D 数组的纹理来利用 3D 局部性。一次性计算附近偏移量的多个结果,以摊销获取数据的成本。获得一个像样的 GPU,因为无论如何你的 GPU 并不比任何最近的 CPU 快多少。
  • 谢谢!是的,我会尝试 3D 阵列,但我对此期望不高。当然,我的 GPU 不是很好……而且我有 Intel Xeon W3520 @ 2.67GHz。嗯...我正在等待我的新显卡(GTX 680),我想我不会有任何优化问题:)
  • 我最希望产生多个输出。因为额外的输出基本上是免费的。
  • @tera 我不明白。您想计算不同 (x,y) 位置的分值吗?
  • 是的。由于相邻像素的得分值只需要获取立方体的一个表面,因此立方体体积中的所有其他值都已加载用于原始像素的得分。

标签: cuda


【解决方案1】:

分析器说什么? NVidia NSight(适用于 Windows 上的 Visual Studio 和适用于 Linux 上的 Eclipse 的插件)可以让你们两个看到停顿的位置,并提供各种提示来优化性能。

我的猜测(没有查看分析器)是您拥有的块太小了。 warp 内部有 32 个线程,这是基本的调度元素。 NVIDIA GPU 能够快速,因为它可以通过在当前线程执行上一条指令时在其他线程上操作来隐藏延迟。虽然每个 SM(在特斯拉和费米上)或 16 个(在开普勒上)可能有 8 个块,但在峰值处仍然有 16-32 个扭曲,这可能非常小(我可能错了,但启动块有一定的延迟)。我会考虑使用更大的块。

如果我正确理解代码,则纹理获取是次优的 - warp 中的线程在 baseFeatIdfeatIdxxxfeatIdx 中的不同之处在于 modelHeight - 1。因此,纹理提取是完全随机的,它不利用数据局部性。颠倒xy 会提高效率。

但是,好的规则是检查分析器 - 如果您的问题是 GPU 上的计算受限,那么您应该专注于计算方面。如果您的问题是内存限制,您应该查看内存访问模式。可能还有其他几个部分看起来像是需要优化的地方,但直到你看到瓶颈是什么你才会知道。一旦你知道了,你可能想阅读best practices guide上的特定章节。

【讨论】:

  • 感谢您的回答!好吧,我已经更新了我的代码,我启动了更大的块 (16x16)。我设法从我的原始非优化代码中获得了 200 毫秒的增益。我知道我加载了两次相同的数据,我想避免它。例如,如果分数X 位置等于x+1 并且模型位置等于xx,它会从纹理中加载与x 位置和xx+1 模型位置相同的数据。我试图避免这种情况,只加载一次。由于我降低了很多获取速度以实现我的加速,我认为这更像是内存限制。
  • @eg141840:如果可能的话,我会走得更高(32*32?),但这是收益递减的。当您通过缓存(常量或纹理)时,重复加载不是问题 - 它们可能只是浪费。您可能希望使用 Kepler 的 __shfl 函数从 32 个连续位置加载并将其广播到各个线程。一般来说,GPU 在内存访问速率方面远远领先于 CPU 和 FPGA,因此一旦您获得了正确的内存访问权限,您应该能够实现很大的加速。
  • 好吧@Maciej Piechotka,我不能像现在的 Quadro FX 1800 那样使用开普勒的功能。但是,是的,计算负载也很大,我想使用更大的块但是我不能,它为我的 GPU 使用了太多寄存器。
  • @eg141840:我对 Tesla(一代)没有太多经验,但如果寄存器太多,它们应该溢出到本地内存 - 不会阻止运行更大的块(而且 - 我猜 - 他们应该流式传输而不是占用寄存器,但我不记得了) - 特别是在编译时完成寄存器分配,而在运行时设置网格大小。就负载而言 - 重要的部分是当一个负载与另一个负载交错时发生的停滞。
猜你喜欢
  • 2016-11-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-21
  • 2012-03-06
  • 1970-01-01
  • 2017-12-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多