Cuda 共享内存、分支性能和寄存器计数
在尝试CUDACuda 共享内存、分支性能和寄存器计数,cuda,Cuda,在尝试CUDAshuffle指令时,我遇到了一些特殊的性能行为。下面的测试内核基于一种图像处理算法,该算法将依赖于输入的值添加到一个正方形rad内的所有相邻像素。每个块的输出都添加到共享内存中。如果每个扭曲只有一个线程将其结果添加到共享内存中,则性能较差(选项1),而另一方面,如果所有线程都添加到共享内存中(一个线程添加所需的值,其余线程仅添加0),则执行时间将下降2-3倍(选项2) #包括 #包括“cuda_runtime.h” #定义warpSz 32 #定义tileY 32 #定义rad
shuffle
指令时,我遇到了一些特殊的性能行为。下面的测试内核基于一种图像处理算法,该算法将依赖于输入的值添加到一个正方形rad
内的所有相邻像素。每个块的输出都添加到共享内存中。如果每个扭曲只有一个线程将其结果添加到共享内存中,则性能较差(选项1),而另一方面,如果所有线程都添加到共享内存中(一个线程添加所需的值,其余线程仅添加0),则执行时间将下降2-3倍(选项2)
#包括
#包括“cuda_runtime.h”
#定义warpSz 32
#定义tileY 32
#定义rad 32
__全局无效测试(浮点*输出,整数变桨)
{
//将共享内存设置为0
__共享浮动瓷砖[(翘曲+2*rad)*(瓷砖+2*rad)];
对于(int i=threadIdx.y*blockDim.x+threadIdx.x;i支持输入Young Bae。我知道,如果线程对同一位置执行写指令,行为是未定义的。由于写缓存,同样的情况也是如此,如果两个线程,即使它们在同一个扭曲中,也在不同的指令中读修改写同一位置。然而,在这里e前31个线程被读取,然后,可能由于缓存而延迟,写回相同的结果。如果缓存的写操作的发布顺序保持不变,那么这是有效的,否则它是未定义的。我在编程指南中没有看到任何关于这一点的内容,有人有参考吗?@jorre“写入共享内存可以缓存”是什么意思?据我所知,没有任何缓存级别的访问延迟小于共享内存。@jorre为了测试为什么选项2工作得更好,我建议监控由于共享内存库冲突而导致的重播次数。我预计选项1会增加。@Young Bae我不确定我是否遵守了。我不认为应该有广播:在选项2中,每个选项都有线程读取(并更新)其唯一的内存位置,该位置在每次迭代中由tile[rowStartIdx+threadIdx.x+j]
给出(+threadIdx.x
和使用blockDim.x
=warpSz
应确保这一点)@VAndrei据我所知,除非使用volatile
关键字声明共享内存,.Re-bank冲突:每次迭代一次读-修改-写(一个bank)应该与32次读-修改-写(在这种情况下,我认为仍然是一个bank)一样快,除非这里还有其他原因。
#include <iostream>
#include "cuda_runtime.h"
#define warpSz 32
#define tileY 32
#define rad 32
__global__ void test(float *out, int pitch)
{
// Set shared mem to 0
__shared__ float tile[(warpSz + 2*rad) * (tileY + 2*rad)];
for (int i = threadIdx.y*blockDim.x+threadIdx.x; i<(tileY+2*rad)*(warpSz+2*rad); i+=blockDim.x*blockDim.y) {
tile[i] = 0.0f;
}
__syncthreads();
for (int row=threadIdx.y; row<tileY; row += blockDim.y) {
// Loop over pixels in neighbourhood
for (int i=0; i<2*rad+1; ++i) {
float res = 0.0f;
int rowStartIdx = (row+i)*(warpSz+2*rad);
for (int j=0; j<2*rad+1; ++j) {
res += float(threadIdx.x+row); // Substitute for real calculation
// Option 1: one thread writes to shared mem
if (threadIdx.x == 0) {
tile[rowStartIdx + j] += res;
res = 0.0f;
}
//// Option 2: all threads write to shared mem
//float tmp = 0.0f;
//if (threadIdx.x == 0) {
// tmp = res;
// res = 0.0f;
//}
//tile[rowStartIdx + threadIdx.x+j] += tmp;
res = __shfl(res, (threadIdx.x+1) % warpSz);
}
res += float(threadIdx.x+row);
tile[rowStartIdx + threadIdx.x+2*rad] += res;
__syncthreads();
}
}
// Add result back to global mem
for (int row=threadIdx.y; row<tileY+2*rad; row+=blockDim.y) {
for (int col=threadIdx.x; col<warpSz+2*rad; col+=warpSz) {
int idx = (blockIdx.y*tileY + row)*pitch + blockIdx.x*warpSz + col;
atomicAdd(out+idx, tile[row*(warpSz+2*rad) + col]);
}
}
}
int main(void)
{
int2 dim = make_int2(512, 512);
int pitchOut = (((dim.x+2*rad)+warpSz-1) / warpSz) * warpSz;
int sizeOut = pitchOut*(dim.y+2*rad);
dim3 gridDim((dim.x+warpSz-1)/warpSz, (dim.y+tileY-1)/tileY, 1);
float *devOut;
cudaMalloc((void**)&devOut, sizeOut*sizeof(float));
cudaEvent_t start, stop;
float elapsedTime;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaFree(0);
cudaEventRecord(start, 0);
test<<<gridDim, dim3(warpSz, 8)>>>(devOut, pitchOut);
cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsedTime, start, stop);
cudaFree(devOut);
cudaDeviceReset();
std::cout << "Elapsed time: " << elapsedTime << " ms.\n";
std::cin.ignore();
}