关于CUDA中每个SM分配的寄存器数量

时间:2013-03-19 09:55:09

标签: cuda

第一个问题。 CUDA C编程指南的编写如下。

  

相同的片上存储器用于L1和共享存储器:它可以   配置为48 KB的共享内存和16 KB的L1缓存或16   共享内存KB和48 KB L1缓存

但是,设备查询显示“每个块可用的寄存器总数:32768”。 我用GTX580。(CC是2.0) 该指南说默认缓存大小为16KB,但32768表示32768 * 4(字节)= 131072字节= 128 KB。实际上,我不知道哪个是正确的。

第二个问题。 我设置如下,

dim3    grid(32, 32);            //blocks in a grid
dim3    block(16, 16);           //threads in a block
kernel<<<grid,block>>>(...);

然后,每个块的线程数是256. =&gt;我们每个块需要256 * N个寄存器。 N表示每个线程所需的寄存器数。 (256 * N)*块是每个SM的寄存器数。(不是字节) 因此,如果默认大小为16KB且threads / SM为MAX(1536),则N不能超过2.由于“每个多处理器的最大线程数:1536”。 16KB / 4Bytes = 4096个寄存器,4096/1536 = 2.66666 ......

如果较大的缓存为48KB,则N不能超过8。 48KB / 4Bytes = 12288个寄存器,12288/1536 = 8

这是真的吗?其实我很困惑。


实际上,我几乎完整的代码就在这里。 我认为,当块尺寸为16x16时,内核会被优化。 但是,在8x8的情况下,快于16x16或类似。 我不知道为什么。

每个线程的寄存器数为16,共享存储器为80 + 16字节。

我曾问过同样的问题,但我无法得到确切的答案: The result of an experiment different from CUDA Occupancy Calculator

#define WIDTH 512
#define HEIGHT 512
#define TILE_WIDTH 8
#define TILE_HEIGHT 8
#define CHANNELS 3
#define DEVICENUM 1 
#define HEIGHTs HEIGHT/DEVICENUM

__global__ void PRINT_POLYGON( unsigned char *IMAGEin, int *MEMin, char a, char b, char c){
        int Col = blockIdx.y*blockDim.y+ threadIdx.y;           //Col is y coordinate
        int Row = blockIdx.x*blockDim.x+ threadIdx.x;           //Row is x coordinate
        int tid_in_block = threadIdx.x + threadIdx.y*blockDim.x;
        int bid_in_grid = blockIdx.x + blockIdx.y*gridDim.x;
        int threads_per_block = blockDim.x * blockDim.y;
        int tid_in_grid = tid_in_block + threads_per_block * bid_in_grid;

        float result_a, result_b;
        __shared__ int M[15];
        for(int k = 0; k < 5; k++){
                M[k] = MEMin[a*5+k];
                M[k+5] = MEMin[b*5+k];
                M[k+10] = MEMin[c*5+k];
        }

        int result_a_up = (M[11]-M[1])*(Row-M[0]) - (M[10]-M[0])*(Col-M[1]);
        int result_b_up = (M[6] -M[1])*(M[0]-Row) - (M[5] -M[0])*(M[1]-Col);

        int result_down = (M[11]-M[1])*(M[5]-M[0]) - (M[6]-M[1])*(M[10]-M[0]);

        result_a = (float)result_a_up / (float)result_down;
        result_b = (float)result_b_up / (float)result_down;

        if((0 <= result_a && result_a <=1) && ((0 <= result_b && result_b <= 1)) && ((0 <= (result_a+result_b) && (result_a+result_b) <= 1))){
                IMAGEin[tid_in_grid*CHANNELS] += M[2] + (M[7]-M[2])*result_a + (M[12]-M[2])*result_b;      //Red Channel
                IMAGEin[tid_in_grid*CHANNELS+1] += M[3] + (M[8]-M[3])*result_a + (M[13]-M[3])*result_b;    //Green Channel
                IMAGEin[tid_in_grid*CHANNELS+2] += M[4] + (M[9]-M[4])*result_a + (M[14]-M[4])*result_b;    //Blue Channel
        }
}

struct DataStruct {
    int                 deviceID;
    unsigned char       IMAGE_SEG[WIDTH*HEIGHTs*CHANNELS];
};

void* routine( void *pvoidData ) { 
        DataStruct  *data = (DataStruct*)pvoidData;
        unsigned char *dev_IMAGE;
        int *dev_MEM;
        unsigned char *IMAGE_SEG = data->IMAGE_SEG;

        HANDLE_ERROR(cudaSetDevice(5));

        //initialize array
        memset(IMAGE_SEG, 0, WIDTH*HEIGHTs*CHANNELS);
        cudaDeviceSetCacheConfig(cudaFuncCachePreferL1);
        printf("Device %d Starting..\n", data->deviceID);

        //Evaluate Time
        cudaEvent_t start, stop;
        cudaEventCreate( &start );
        cudaEventCreate( &stop );

        cudaEventRecord(start, 0); 

        HANDLE_ERROR( cudaMalloc( (void **)&dev_MEM, sizeof(int)*35) );
        HANDLE_ERROR( cudaMalloc( (void **)&dev_IMAGE, sizeof(unsigned char)*WIDTH*HEIGHTs*CHANNELS) );

        cudaMemcpy(dev_MEM, MEM, sizeof(int)*35, cudaMemcpyHostToDevice);
        cudaMemset(dev_IMAGE, 0, sizeof(unsigned char)*WIDTH*HEIGHTs*CHANNELS);

        dim3    grid(WIDTH/TILE_WIDTH, HEIGHTs/TILE_HEIGHT);            //blocks in a grid
        dim3    block(TILE_WIDTH, TILE_HEIGHT);                         //threads in a block

        PRINT_POLYGON<<<grid,block>>>( dev_IMAGE, dev_MEM, 0, 1, 2);
        PRINT_POLYGON<<<grid,block>>>( dev_IMAGE, dev_MEM, 0, 2, 3);
        PRINT_POLYGON<<<grid,block>>>( dev_IMAGE, dev_MEM, 0, 3, 4);
        PRINT_POLYGON<<<grid,block>>>( dev_IMAGE, dev_MEM, 0, 4, 5);
        PRINT_POLYGON<<<grid,block>>>( dev_IMAGE, dev_MEM, 3, 2, 4);
        PRINT_POLYGON<<<grid,block>>>( dev_IMAGE, dev_MEM, 2, 6, 4);

        HANDLE_ERROR( cudaMemcpy( IMAGE_SEG, dev_IMAGE, sizeof(unsigned char)*WIDTH*HEIGHTs*CHANNELS, cudaMemcpyDeviceToHost ) );
        HANDLE_ERROR( cudaFree( dev_MEM ) );
        HANDLE_ERROR( cudaFree( dev_IMAGE ) );

        cudaEventRecord(stop, 0); 
        cudaEventSynchronize(stop);

        cudaEventElapsedTime( &elapsed_time_ms[data->deviceID], start, stop );
        cudaEventDestroy(start);
        cudaEventDestroy(stop);


        elapsed_time_ms[DEVICENUM] += elapsed_time_ms[data->deviceID];
        printf("Device %d Complete!\n", data->deviceID);

        return 0;
}

2 个答案:

答案 0 :(得分:2)

blockDim 8x8比16x16更快,因为当你增加块大小时,你的内存访问中的地址差异会增加。

使用15个SM在GTX480上收集的指标。

metric                         8x8         16x16
duration                        161µs       114µs
issued_ipc                     1.24        1.31
executed_ipc                    .88         .59
serialization                 54.61%      28.74%

指令重放的次数使我们觉得我们可能有不良的内存访问模式。

achieved occupancy            88.32%      30.76%
0 warp schedulers issues       8.81%       7.98%
1 warp schedulers issues       2.36%      29.54%
2 warp schedulers issues      88.83%      52.44%

16x16似乎使warp调度程序忙。但是,它会让调度程序忙于重新发出指令。

l1 global load trans          524,407     332,007
l1 global store trans         401,224     209,139
l1 global load trans/request    3.56        2.25
l1 global store trans/request  16.33        8.51

首要任务是减少每个请求的事务。 Nsight VSE源视图可以显示每条指令的内存统计信息。内核中的主要问题是交错的U8加载并存储IMAGEin [] + = value。在16x16时,每个请求产生16.3个事务,但8x8配置只产生8.3个事务。

更改     IMAGEin [(i * HEIGHTs + j)* CHANNELS] + = ...

连续将16x16的性能提高3倍。我想将通道增加到4并在内核中处理打包将提高缓存性能和内存吞吐量。

如果您修复了每个请求的内存事务数,那么您可能需要查看执行依赖项并尝试增加ILP。

答案 1 :(得分:1)

块大小为8x8时速度更快,因为它是32的较小倍数,因为它在下图中可见,有32个CUDA核心绑定在一起,两个不同的warp调度程序实际上安排了同样的事情。因此,在每个执行周期中,在这32个内核上执行相同的指令。

为了更好地澄清这一点,在第一种情况下(8x8),每个块由两个warp(64个线程)组成,因此它只在两个执行周期内完成,但是,当你使用(16x16)作为块大小时,每个经线需要8个经线(256个线程),因此执行周期增加4倍,导致化合物变慢。

然而,在某些情况下,填充更多warp的SM会更好,当内存访问很高并且每个warp可能进入内存停顿(即从内存中获取其操作数)时,它将被另一个warp替换直到内存操作完成。因此导致SM占用更多。

当然,您应该在计算中输入每个SM的块数和SM总数,例如,为单个SM分配超过8个块可能会减少其占用率,但可能在您的情况下,您不是面对这些问题,因为256通常比64更好,因为它会在SM之间平衡你的块,而使用64个线程将导致更多的块在同一个SM中执行。

编辑:这个答案是基于我的推测,对于更科学的方法,请参阅Greg Smiths的回答。

注册池与共享内存/缓存不同,直到他们架构的最底层!

注册由Flip-flops组成,L1缓存可能是SRAM

只是想知道,看看下面代表FERMI架构的图片,然后更新你的问题以进一步说明你所面临的问题。

FERMI Architecture

作为一个注释,您可以通过将选项--ptxas-options = -v传递给nvcc来查看函数占用了多少寄存器和共享内存(smem)。