使用CUDA计算积分图像比使用CPU代码慢

时间:2014-08-13 17:09:09

标签: c++ performance image-processing cuda textures

我正在使用CUDA实现积分图像计算模块以提高性能。 但它的速度比CPU模块慢。 请让我知道我做错了什么。 cuda内核和主机代码如下。 而且,另一个问题是...... 在内核SumH中,使用纹理内存比全局内容慢,imageTexture定义如下。

texture<unsigned char, 1> imageTexture;
cudaBindTexture(0, imageTexture, pbImage);

//内核水平和垂直扫描图像。

__global__ void SumH(unsigned char* pbImage, int* pnIntImage, __int64* pn64SqrIntImage, float rVSpan, int nWidth)
{
    int nStartY, nEndY, nIdx;
    if (!threadIdx.x)
    {
        nStartY = 1;
    }
    else
        nStartY = (int)(threadIdx.x * rVSpan);
    nEndY = (int)((threadIdx.x + 1) * rVSpan);

    for (int i = nStartY; i < nEndY; i ++)
    {
        for (int j = 1; j < nWidth; j ++)
        {
            nIdx = i * nWidth + j;
            pnIntImage[nIdx] = pnIntImage[nIdx - 1] + pbImage[nIdx - nWidth - i];
            pn64SqrIntImage[nIdx] = pn64SqrIntImage[nIdx - 1] + pbImage[nIdx - nWidth - i] * pbImage[nIdx - nWidth - i];
            //pnIntImage[nIdx] = pnIntImage[nIdx - 1] + tex1Dfetch(imageTexture, nIdx - nWidth - i);
            //pn64SqrIntImage[nIdx] = pn64SqrIntImage[nIdx - 1] + tex1Dfetch(imageTexture, nIdx - nWidth - i) * tex1Dfetch(imageTexture, nIdx - nWidth - i);
        }
    }
}
__global__ void SumV(unsigned char* pbImage, int* pnIntImage, __int64* pn64SqrIntImage, float rHSpan, int nHeight, int nWidth)
{
    int nStartX, nEndX, nIdx;
    if (!threadIdx.x)
    {
        nStartX = 1;
    }
    else
        nStartX = (int)(threadIdx.x * rHSpan);
    nEndX = (int)((threadIdx.x + 1) * rHSpan);

    for (int i = 1; i < nHeight; i ++)
    {
        for (int j = nStartX; j < nEndX; j ++)
        {
            nIdx = i * nWidth + j;
            pnIntImage[nIdx] = pnIntImage[nIdx - nWidth] + pnIntImage[nIdx];
            pn64SqrIntImage[nIdx] = pn64SqrIntImage[nIdx - nWidth] + pn64SqrIntImage[nIdx];
        }
    }
}

//主机代码

    int nW = image_width;
    int nH = image_height;
    unsigned char* pbImage;
    int* pnIntImage;
    __int64* pn64SqrIntImage;
    cudaMallocManaged(&pbImage, nH * nW);
    // assign image gray values to pbimage
    cudaMallocManaged(&pnIntImage, sizeof(int) * (nH + 1) * (nW + 1));
    cudaMallocManaged(&pn64SqrIntImage, sizeof(__int64) * (nH + 1) * (nW + 1));
    float rHSpan, rVSpan;
        int nHThreadNum, nVThreadNum;
        if (nW + 1 <= 1024)
        {
            rHSpan = 1;
            nVThreadNum = nW + 1;
        }
        else
        {
            rHSpan = (float)(nW + 1) / 1024;
            nVThreadNum = 1024;
        }
        if (nH + 1 <= 1024)
        {
            rVSpan = 1;
            nHThreadNum = nH + 1;
        }
        else
        {
            rVSpan = (float)(nH + 1) / 1024;
            nHThreadNum = 1024;
        }

        SumH<<<1, nHThreadNum>>>(pbImage, pnIntImage, pn64SqrIntImage, rVSpan, nW + 1);
        cudaDeviceSynchronize();
        SumV<<<1, nVThreadNum>>>(pbImage, pnIntImage, pn64SqrIntImage, rHSpan, nH + 1, nW + 1);
        cudaDeviceSynchronize();

4 个答案:

答案 0 :(得分:2)

关于当前问题中的代码。我想提及两件事:启动参数和时序方法。

1)启动参数

启动内核时,有两个主要参数指定要启动的线程数。它们位于<<<>>>部分之间,是网格中的块数,以及每个块的线程数,如下所示:

foo <<< numBlocks, numThreadsPerBlock >>> (args);

要使单个内核在当前GPU上高效,您可以使用numBlocks * numThreadsPerBlock应至少为10,000的经验法则。 IE浏览器。 10,000件工作。这是一个经验法则,因此只有5,000个线程可以获得良好的结果(它随GPU而变化:更便宜的GPU可以用更少的线程消失),但这是您需要将其视为最小值的数量级。 你正在运行1024个线程。这几乎肯定是不够的(提示:内核中的循环看起来像扫描原型,这些可以并行完成)。

除此之外,还有一些其他事项需要考虑。

  • 与GPU上的SM数量相比,块数应该很大。 Kepler K40有15个SM,为了避免显着tail effect,你可能想要在这个GPU上至少约100个块。其他GPU的SM较少,但你没有指定你拥有的,所以我不能更具体。
  • 每个块的线程数不应太小。每个SM上只能有这么多块,所以如果你的块太小,你将使用次优的GPU。此外,在较新的GPU up to four warps can receive instructions on a SM simultaneously上,因此将块大小设置为128的倍数通常是个好主意。

2)时间

我不打算在这里深入探讨,但要确保你的时机安稳。 GPU代码往往具有一次性初始化延迟。如果这在您的时间范围内,您将看到错误的大运行时代码,旨在表示更大的代码。同样,CPU和GPU之间的数据传输也需要时间。在实际的应用程序中,您只能对数千次内核调用执行此操作,但在测试应用程序中,您可以在每次内核启动时执行一次。

如果您想获得准确的时间安排,您必须使您的示例更能代表最终代码,或者您必须确保只对将要重复的区域进行计时。

答案 1 :(得分:0)

使用CUDA时,您应该记住一些事项。

  1. 从主机内存复制到设备内存“慢” - 当您将一些数据从主机复制到设备时,您应该尽可能多地进行计算(完成所有工作),然后再将其复制回主机。
  2. 在设备上有3种类型的内存 - 全局,共享,本地。你可以像全球一样对它们进行排名&lt;分享&lt;本地(本地=最快)。
  3. 从连续内存块中读取比随机访问更快。使用结构数组时,您希望将其转换为数组结构。
  4. 您可以随时咨询CUDA Visual Profiler,向您展示您的计划的瓶颈。

答案 2 :(得分:0)

上面提到的GTX750有512个CUDA核心(这些与着色器单元相同,只是以/不同/模式驱动)。 http://www.nvidia.de/object/geforce-gtx-750-de.html#pdpContent=2

创建整体图像的任务只能部分地并行化,因为结果数组中的任何结果值都取决于它的前辈们。此外,每次内存传输只是一个微小的数学部分,因此ALU供电,因此不可避免的内存传输可能是瓶颈。这样的加速器可能提供一些加速,但不是一个惊心动魄的加速,因为责任本身不允许它。

如果您要在同一输入数据上计算多个积分图像变体,您将能够看到&#34;刺激&#34;更可能是由于更高的并行选项和更高的数学运算量。但那将是一个不同的职责。

谷歌搜索的疯狂猜测 - 其他人已经摆弄了这些项目:https://www.google.de/url?sa=t&rct=j&q=&esrc=s&source=web&cd=11&cad=rja&uact=8&ved=0CD8QFjAKahUKEwjjnoabw8bIAhXFvhQKHbUpA1Y&url=http%3A%2F%2Fdspace.mit.edu%2Fopenaccess-disseminate%2F1721.1%2F71883&usg=AFQjCNHBbOEB_OHAzLZI9__lXO_7FPqdqA

答案 3 :(得分:-1)

唯一可以确定的方法是对代码进行分析,但在这种情况下,我们可能会做出合理的猜测。

您基本上只是对一些数据执行单次扫描,并对每个项目执行极少的处理。

鉴于您对每个项目进行的处理很少,使用CPU处理数据时的瓶颈可能只是从内存中读取数据。

当您在GPU上进行处理时,仍需要从内存中读取数据并将其复制到GPU的内存中。这意味着我们仍然需要从主内存中读取所有数据,就像CPU进行处理一样。更糟糕的是,这一切都必须写入GPU的内存,导致进一步放缓。当GPU甚至开始进行实际处理时,你已经耗费了比CPU完成工作所花费的更多时间。

对于Cuda来说,您通常需要对每个单独的数据项进行更多处理。在这种情况下,CPU大部分时间可能已经几乎处于空闲状态,等待内存中的数据。在这种情况下,GPU不太可能提供很多帮助除非输入数据已经存在于GPU的内存中,因此GPU可以在没有任何额外复制的情况下进行处理。