CUDA版本比CPU版本慢吗?

时间:2011-02-05 18:25:13

标签: cuda

我正在CUDA中编写一个图像子采样器,并使用这些线程来执行平均操作。但是,如果我这样做而不调用内核,它比我实际调用CUDA内核时运行得快得多。图像大小现在是1280x1024 。 内核调用通常需要很长时间,还是我的实现有问题?

PS我尝试只调用内核(删除代码),它与内核代码几乎相同。同样,我的代码没有内核调用运行大约350毫秒,而内核调用运行接近1000毫秒。

__global__ void subsampler(int *r_d,int *g_d,int *b_d, int height,int width,int *f_r,int*f_g,int*f_b){ 
        int id=blockIdx.x * blockDim.x*blockDim.y+ threadIdx.y*blockDim.x+threadIdx.x+blockIdx.y*gridDim.x*blockDim.x*blockDim.y;
        if (id<height*width/4){
        f_r[id]=(r_d[4*id]+r_d[4*id+1]+r_d[4*id+2]+r_d[4*id+3])/4;
        f_g[id]=(g_d[4*id]+g_d[4*id+1]+g_d[4*id+2]+g_d[4*id+3])/4;
        f_b[id]=(b_d[4*id]+b_d[4*id+1]+b_d[4*id+2]+b_d[4*id+3])/4;
        }
        }

我将blockSizeX和blockSizeY定义为1和1(我尝试将它们设为4,16)但不知何故这是最快的

 dim3 blockSize(blocksizeX,blocksizeY);
  int new_width=img_width/2;
  int new_height=img_height/2;

  int n_blocks_x=new_width/blocksizeX+(new_width/blocksizeY == 0 ?0:1);
  int n_blocks_y=new_height/blocksizeX+(new_height/blocksizeY == 0 ?0:1);
  dim3 gridSize(n_blocks_x,n_blocks_y);

然后我用gridSize,BlockSize调用内核。

2 个答案:

答案 0 :(得分:2)

可能内核没有很好地实现,或者可能是将数据移入和移出GPU卡的开销正在淹没任何计算优势。尝试单独对内核进行基准测试(没有CPU&lt; - &gt; GPU内存传输),以查看内核占用的总时间和内存传输的时间。然后,您可以根据这些度量来决定是否需要在内核上做更多的工作。

答案 1 :(得分:0)

虽然我不确定你运行的硬件是什么,但你应该能够使这个内核的性能接近1000 fps,而不是1000ms /帧:)

建议1:如果此处理与可视化有任何交互,通过OpenGL / DirectX或类似方法,只需将其作为着色器进行 - 网格/块大小,内存布局等的所有细节都将为您处理。如果您真的需要在CUDA中自己实现,请继续阅读:

首先,我假设您在每个方向上将1280x1024图像二次采样2倍,产生640x512图像。结果图像中的每个像素是源图像中四个像素的平均值。图像有三个通道RGB。

问题1 :您真的想要32位每个频道还是想要RGB888(每个频道8位)? RGB888相当普遍 - 我会假设这就是你的意思。

问题2 :您的数据实际上是平面的,还是从交错格式中提取数据? RGB888是一种交错格式,其中像素在内存中显示为RGBRGBRGB。我会编写你的内核来处理它的原生格式的图像。我假设您的数据实际上是平面的,因此您有三个平面,R8,G8和B8。

要做的第一件事是考虑内存布局。您将需要一个线程用于目标图像中的每个像素。鉴于子采样的内存访问模式未合并,您将需要将像素数据读入共享内存。考虑32x8线程的块大小。这允许每个块以40 * 8 * 4像素读取,或者以3bpp读取3072个字节。实际上,您将读取更多内容,以保持负载合并,每个块总共4096个字节。这现在给你:

dim3 block(32, 8);
dim3 grid(1280 / 2 / 32, 1024 / 2 / 8); // 20x64 blocks of 256 threads

现在来了有趣的部分:做共享内存。你的内核可能如下所示:

__global__ void subsample(uchar* r, uchar* g, uchar* b,    // in
                          uchar* ro, uchar* go, uchar* bo) // out
{
    /* Global offset into output pixel arrays */
    int gid = blockIdx.y * gridDim.x * blockDim.x + blockIdx.x * blockDim.x;

    /* Global offset into input pixel arrays */
    int gidin = gid * 2;

    __shared__ uchar* rc[1024];
    __shared__ uchar* gc[1024];
    __shared__ uchar* bc[1024];

    /* Read r, g, and b, into shmem cache */
    ((int*)rc)[threadIdx.x] = ((int*)r)[gidin + threadIdx.x];
    ((int*)gc)[threadIdx.x] = ((int*)g)[gidin + threadIdx.x];
    ((int*)bc)[threadIdx.x] = ((int*)b)[gidin + threadIdx.x];

    __syncthreads();

    /* Shared memory for output */
    __shared__ uchar* roc[256];
    __shared__ uchar* goc[256];
    __shared__ uchar* boc[256];

    /* Do the subsampling, one pixel per thread. Store into the output shared memory */

    ...

    __syncthreads();

    /* Finally, write the result to global memory with coalesced stores */
    if (threadIdx.x < 64) {
        ((int*)ro)[gid + threadIdx.x] =  ((int*)roc)[threadIdx.x];
    } else if (threadIdx.x < 128) {
        ((int*)go)[gid + threadIdx.x-64] =  ((int*)goc)[threadIdx.x-64];
    } else if (threadIdx.x < 192) {
        ((int*)bo)[gid + threadIdx.x-128] =  ((int*)boc)[threadIdx.x-128];
    }
}

呼!很多东西在那里,抱歉代码转储。要牢记的一些原则:

1)使用合并的加载/存储时内存很快。这意味着对于warp为32的每个线程,每个线程访问32个字节。如果32byte索引与warp中的线程索引匹配,那么所有32个访问都将被放入一个128事务中。这就是你获得GPU的100GB / s带宽的方法。

2)进行子采样时的内存访问模式未合并,因为它依赖于原始内存所没有的2D空间局部性。 (也可以使用纹理内存......)通过将输入存储在共享内存中,然后进行处理,可以最大限度地降低对计算性能的影响。

我希望这有帮助 - 如果您愿意,我可以回复一些部分的更多细节。