我想通过BS_x读取(BS_X + 1)*(BS_Y + 1)全局内存位置* BS_Y线程将内容移动到共享内存,我开发了以下代码。
int i = threadIdx.x;
int j = threadIdx.y;
int idx = blockIdx.x*BLOCK_SIZE_X + threadIdx.x;
int idy = blockIdx.y*BLOCK_SIZE_Y + threadIdx.y;
int index1 = j*BLOCK_SIZE_Y+i;
int i1 = (index1)%(BLOCK_SIZE_X+1);
int j1 = (index1)/(BLOCK_SIZE_Y+1);
int i2 = (BLOCK_SIZE_X*BLOCK_SIZE_Y+index1)%(BLOCK_SIZE_X+1);
int j2 = (BLOCK_SIZE_X*BLOCK_SIZE_Y+index1)/(BLOCK_SIZE_Y+1);
__shared__ double Ezx_h_shared_ext[BLOCK_SIZE_X+1][BLOCK_SIZE_Y+1];
Ezx_h_shared_ext[i1][j1]=Ezx_h[(blockIdx.y*BLOCK_SIZE_Y+j1)*xdim+(blockIdx.x*BLOCK_SIZE_X+i1)];
if ((i2<(BLOCK_SIZE_X+1))&&(j2<(BLOCK_SIZE_Y+1)))
Ezx_h_shared_ext[i2][j2]=Ezx_h[(blockIdx.y*BLOCK_SIZE_Y+j2)*xdim+(blockIdx.x*BLOCK_SIZE_X+i2)];
根据我的理解,合并是顺序处理的连续内存读取的并行等价物。现在如何检测全局内存访问是否已合并?我注意到从(i1,j1)到(i2,j2)的索引跳转。 提前谢谢。
答案 0 :(得分:5)
我用手写的合并分析器评估了代码的内存访问。评估显示代码较少利用合并。以下是您可能会觉得有用的合并分析器:
#include <stdio.h>
#include <malloc.h>
typedef struct dim3_t{
int x;
int y;
} dim3;
// KERNEL LAUNCH PARAMETERS
#define GRIDDIMX 4
#define GRIDDIMY 4
#define BLOCKDIMX 16
#define BLOCKDIMY 16
// ARCHITECTURE DEPENDENT
// number of threads aggregated for coalescing
#define COALESCINGWIDTH 32
// number of bytes in one coalesced transaction
#define CACHEBLOCKSIZE 128
#define CACHE_BLOCK_ADDR(addr,size) (addr*size)&(~(CACHEBLOCKSIZE-1))
int main(){
// fixed dim3 variables
// grid and block size
dim3 blockDim,gridDim;
blockDim.x=BLOCKDIMX;
blockDim.y=BLOCKDIMY;
gridDim.x=GRIDDIMX;
gridDim.y=GRIDDIMY;
// counters
int unq_accesses=0;
int *unq_addr=(int*)malloc(sizeof(int)*COALESCINGWIDTH);
int total_unq_accesses=0;
// iter over total number of threads
// and count the number of memory requests (the coalesced requests)
int I, II, III;
for(I=0; I<GRIDDIMX*GRIDDIMY; I++){
dim3 blockIdx;
blockIdx.x = I%GRIDDIMX;
blockIdx.y = I/GRIDDIMX;
for(II=0; II<BLOCKDIMX*BLOCKDIMY; II++){
if(II%COALESCINGWIDTH==0){
// new coalescing bunch
total_unq_accesses+=unq_accesses;
unq_accesses=0;
}
dim3 threadIdx;
threadIdx.x=II%BLOCKDIMX;
threadIdx.y=II/BLOCKDIMX;
////////////////////////////////////////////////////////
// Change this section to evaluate different accesses //
////////////////////////////////////////////////////////
// do your indexing here
#define BLOCK_SIZE_X BLOCKDIMX
#define BLOCK_SIZE_Y BLOCKDIMY
#define xdim 32
int i = threadIdx.x;
int j = threadIdx.y;
int idx = blockIdx.x*BLOCK_SIZE_X + threadIdx.x;
int idy = blockIdx.y*BLOCK_SIZE_Y + threadIdx.y;
int index1 = j*BLOCK_SIZE_Y+i;
int i1 = (index1)%(BLOCK_SIZE_X+1);
int j1 = (index1)/(BLOCK_SIZE_Y+1);
int i2 = (BLOCK_SIZE_X*BLOCK_SIZE_Y+index1)%(BLOCK_SIZE_X+1);
int j2 = (BLOCK_SIZE_X*BLOCK_SIZE_Y+index1)/(BLOCK_SIZE_Y+1);
// calculate the accessed location and offset here
// change the line "Ezx_h[(blockIdx.y*BLOCK_SIZE_Y+j1)*xdim+(blockIdx.x*BLOCK_SIZE_X+i1)];" to
int addr = (blockIdx.y*BLOCK_SIZE_Y+j1)*xdim+(blockIdx.x*BLOCK_SIZE_X+i1);
int size = sizeof(double);
//////////////////////////
// End of modifications //
//////////////////////////
printf("tid (%d,%d) from blockid (%d,%d) accessing to block %d\n",threadIdx.x,threadIdx.y,blockIdx.x,blockIdx.y,CACHE_BLOCK_ADDR(addr,size));
// check whether it can be merged with existing requests or not
short merged=0;
for(III=0; III<unq_accesses; III++){
if(CACHE_BLOCK_ADDR(addr,size)==CACHE_BLOCK_ADDR(unq_addr[III],size)){
merged=1;
break;
}
}
if(!merged){
// new cache block accessed over this coalescing width
unq_addr[unq_accesses]=CACHE_BLOCK_ADDR(addr,size);
unq_accesses++;
}
}
}
printf("%d threads make %d memory transactions\n",GRIDDIMX*GRIDDIMY*BLOCKDIMX*BLOCKDIMY, total_unq_accesses);
}
代码将针对网格的每个线程运行,并计算合并请求的数量,内存访问合并的度量标准。
要使用分析器,请将代码的索引计算部分粘贴到指定区域,并将内存访问(数组)分解为“地址”和“大小”。我已经为你的代码做了这个,索引是:
int i = threadIdx.x;
int j = threadIdx.y;
int idx = blockIdx.x*BLOCK_SIZE_X + threadIdx.x;
int idy = blockIdx.y*BLOCK_SIZE_Y + threadIdx.y;
int index1 = j*BLOCK_SIZE_Y+i;
int i1 = (index1)%(BLOCK_SIZE_X+1);
int j1 = (index1)/(BLOCK_SIZE_Y+1);
int i2 = (BLOCK_SIZE_X*BLOCK_SIZE_Y+index1)%(BLOCK_SIZE_X+1);
int j2 = (BLOCK_SIZE_X*BLOCK_SIZE_Y+index1)/(BLOCK_SIZE_Y+1);
并且内存访问是:
Ezx_h_shared_ext[i1][j1]=Ezx_h[(blockIdx.y*BLOCK_SIZE_Y+j1)*xdim+(blockIdx.x*BLOCK_SIZE_X+i1)];
分析器报告4096个线程访问4064个缓存块。运行实际网格和块大小的代码并分析合并行为。
答案 1 :(得分:2)
随着GPU的发展,获得合并访问的要求变得越来越不严格。对于早期的GPU架构,您对合并访问的描述比最近的GPU架构更准确。特别是,Fermi(计算能力2.0)显着放宽了要求。在费米及以后,连续访问存储器位置并不重要。相反,焦点已转移到使用尽可能少的内存事务访问内存。在Fermi上,全局内存事务是128字节宽。因此,当warp中的32个线程命中执行加载或存储的指令时,将调度128字节的事务来为warp中的所有线程提供服务。然后,性能取决于需要多少交易。如果所有线程都访问128字节区域内与128字节对齐的值,则需要进行单个事务。如果所有线程都访问不同128字节区域中的值,则需要32个事务。这将是在warp中为单个指令提供服务的最坏情况。
您可以使用其中一个CUDA分析器来确定为请求提供服务所需的事务数的平均值。该数字应尽可能接近1。数字越大意味着您应该看看是否有机会优化内核中的内存访问。
答案 2 :(得分:1)
visual profiler是检查工作的绝佳工具。在功能正确的代码片段之后,然后从可视化分析器中运行它。例如,在Linux上,假设你有一个X会话,只需从终端窗口运行nvvp。然后,您将获得一个向导,该向导将提示您应用程序以及任何命令行参数进行分析。
然后,探查器将对您的应用进行基本运行以收集统计信息。您还可以选择更高级的统计信息收集(需要添加运行),其中一个将是内存利用率统计信息。它会将内存利用率报告为峰值的百分比,并且还会标记其认为低利用率的警告,值得您关注。
如果您的利用率超过50%,那么您的应用可能正在以您期望的方式运行。如果你的数字很少,你可能错过了一些合并细节。它将分别报告内存读取和内存写入的统计信息。要获得100%或接近它,您还需要确保经线的合并读取和写入在128字节边界上对齐。
在这些情况下常见的错误是使用基于threadIdx.y的变量作为最快速变化的索引。在我看来你没有犯这个错误。例如这是shared[threadIdx.x][threadIdx.y]
的常见错误,因为这通常是我们在C中考虑它的方式。但是线程首先在x轴上组合在一起,所以我们想要使用shared[threadIdx.y][threadIdx.x]
或类似的东西。如果您确实犯了这个错误,那么您的代码仍然可以在功能上正确,但在分析器中您将获得较低的利用率百分比,例如大约12%甚至3%。
如前所述,要达到50%以上且接近100%,您需要确保不仅所有线程请求都相邻,而且它们在128B边界上对齐。由于L1 / L2缓存,这些并不是硬性规定,而是指导方针。缓存可以在某种程度上缓解一些错误。