毫无意义地使用CUDA共享内存

时间:2014-11-15 20:59:57

标签: memory cuda shared convolution

我有两个版本的相同算法。它最初是卷积,但我修改它以减少它以检查我的瓶颈在哪里(注意每个循环只能访问一次全局内存):

__global__
void convolve (unsigned char * Md, float * Kd, unsigned char * Rd, int width, int height, int kernel_size, int tile_width, int channels){

int row = blockIdx.y*tile_width + threadIdx.y;
int col = blockIdx.x*tile_width + threadIdx.x;

int sum = 0;
int pixel;
int local_pixel;
int working_pixel;

int row_offset = (kernel_size/2)*(width+kernel_size-1);
int col_offset = kernel_size/2;

for(int color=0; color<channels; color++){

    pixel = color*width*height + row*width + col;
    local_pixel = color*(width+kernel_size-1)*(height+kernel_size-1) + row*(width+kernel_size-1) + col + row_offset + col_offset;
    if(row < height  &&  col < width){
        for(int x=(-1)*kernel_size/2; x<=kernel_size/2; x++)
            for(int y=(-1)*kernel_size/2; y<=kernel_size/2; y++){
                working_pixel = local_pixel + x + y*(width+kernel_size-1);
                sum += (int)((float)Md[working_pixel]);// * ((float)Kd[x+kernel_size/2 + (y+kernel_size/2)*kernel_size]);

            }
        Rd[pixel] = (int) sum;
        sum = 0;
    }
}
}

这是共享内存版本(每个循环一次访问共享内存)

__global__
void convolve (unsigned char * Md, float * Kd, unsigned char * Rd, int width, int height, int kernel_size, int tile_width, int channels){

__shared__ unsigned char Mds[256 + 16*4 +4];

int row = blockIdx.y*tile_width + threadIdx.y;
int col = blockIdx.x*tile_width + threadIdx.x;

if(row < height  &&  col < width){

    int sum = 0;
    int pixel;  //the pixel to copy from Md (the input image)
    int local_pixel;    //the pixel in shared memory

    int start_pixel;    //the offset to copy the borders

    int mds_width = tile_width+kernel_size-1;
    int md_width = width+kernel_size-1;
    int md_height = height+kernel_size-1;

    for(int color=0; color<channels; color++){

        pixel = color*md_width*md_height + row*md_width + col  +  (kernel_size/2)*md_width + kernel_size/2; //position (including borders) + offset
        local_pixel = threadIdx.y*mds_width + threadIdx.x  +  (kernel_size/2)*mds_width + kernel_size/2;    //position + offset


        //Loading the pixels
        Mds[local_pixel] = Md[pixel];//bringing the central pixel itself (position + offset)


        __syncthreads();

        //Convolving
        for(int x=(-1)*kernel_size/2; x<=kernel_size/2; x++)
            for(int y=(-1)*kernel_size/2; y<=kernel_size/2; y++)
                sum += (int)((float)Mds[local_pixel + x + y*mds_width]); // * ((float)Kd[x+kernel_size/2 + (y+kernel_size/2)*kernel_size]);
        Rd[color*width*height + row*width + col] = (int) sum;
        sum = 0;

        __syncthreads();

    }
}
}

执行参数是

convolve<<<dimGrid,dimBlock>>>(Md,Kd,Rd,width,new_height,kernel_size,block_size,colors);

dimGrid = (1376,768)
dimBlock = (16,16)
Md is the read only image
Kd is the filter (3x3)
width = 22016
height = 12288
kernel_size = 3
block_size=16
colors=3

我用第一个算法得到1249.59毫秒,用第二个算法得到1178.2毫秒,我觉得很荒谬。 我认为寄存器的数量应该不成问题。用ptxas编译我得到:

ptxas info: 560 bytes gmem, 52 bytes cmem[14]
ptxas info: Compiling entry function '_Z8convolvePhPfS_iiiii' for 'sm_10'
ptxas info: Used 16 registers, 384 bytes smem, 4 bytes cmem[1]

虽然我的设备信息是:

Name: GeForce GTX 660 Ti
Minor Compute Capability: 0
Major Compute Capability: 3
Warp Size: 32
Max Treads per Block: 1024
Max Threads Dimension: (1024,1024,64)
Max Grid Size: (2147483647,65535,65535)
Number of SM: 7
Max Threads Per SM: 2048
Regs per Block (SM): 65536
Total global Memory: 2146762752
Shared Memory per Block: 49152

有没有人对这种糟糕的改善有任何暗示?我不认识其他任何人......

编辑: 我今天使用了不同的nvidia卡,因为我无法访问实验室。它还具有3.0的计算能力。 我把两个if语句放在循环之外。 我正在使用-arch compute_30 -code sm_30进行编译 我删除了所有的铸件。 全局矩阵现在声明为const unsigned char * restrict Md 我这次使用的是一个9x9过滤器,它可以让每个像素在被带入共享内存后重复使用81次。

我从终端获得3138.41 ms(全球版本)和3120.96 ms(共享版本)。 在视觉分析器中,它需要更长的时间。这就是我得到的(截图) http://cl.ly/image/1X372l242S2u

像我一样迷失了......

请在这里找到这个算法易于编译和执行:

http://cl.ly/213l2X3S1v3a

./ convolution 8000 4000 159 9 edge_detection_9.txt 0表示全局内存版本 ./convolution 8000 4000 159 9 edge_detection_9.txt 1用于共享内存版本

1 个答案:

答案 0 :(得分:5)

引起我注意的第一件事是:

ptxas info: Compiling entry function '_Z8convolvePhPfS_iiiii' for 'sm_10'

您的卡具有计算能力3.0,因此您应该使用 sm_30 进行编译。 sm_10缺少sm_30的许多功能,共享内存较小,寄存器较少。


接下来我要做的是将if语句放在for循环之外的两个内核中,以进行正确的内核比较。


接下来,我会增加kernel_size以突出显示共享内存的影响。您的内核中只有9次访问(,如果我计算正确),这意味着以下内容:

  • 在第一个内核中,您将全局内存中的元素直接读入寄存器并使用它们
  • 在第二个内核中,每个线程从全局内存中读取一个元素,每个线程在共享缓存中进行9次访问。
  • 由于您没有对共享缓存中的元素进行大量重用,因此您为访问全局内存而支付的价格太高了。

此外,sum += (int)((float)Mds[local_pixel + x + y*mds_width]);会在共享缓存中产生一些银行冲突,从而降低其吞吐量。

如果你的kernel_size总是3,你也可以通过展开它们并使用固定索引替换for循环来帮助编译器。


我还关注从uchar到float到的强制转换的惩罚。我知道这些操作成本很高,进一步降低了共享缓存使用量。你为什么选择(int) sum;,因为Rd是unsigned char?为什么不将Rd声明为int *?


我看到你的内核也需要Kd。正如它现在宣布的那样,它存储在全局存储器中。如果它只是一个3x3过滤器,你可以对它进行硬编码或者在一个线程局部变量中将它加载到循环之外,这有可能被存储到寄存器中。

如果这不起作用,您可以尝试将系数存储到共享内存中。实际上复制了系数。每个线程都有,以避免银行冲突。共享内存在Kepler上有32个端口,因此可以在warp中的所有线程上同时提取系数。


总之我认为您的共享缓存内核会使用sm_10和多个类型转换来支付进入全局内存,共享内存库冲突的代价,从而大大降低共享缓存的收益。一般建议,使用 CUDA Visual Profiler 来验证这些点。


另外,我会尝试使用纹理缓存,将 Md 声明为const __restrict__。与全局内存访问相比,这可能显示出一些加速,因为它是一个多端口缓存,具有旨在减少银行冲突的特殊映射。实际上我希望这比共享内存情况更好。