一、共享內存的結構
1)什麼是共享內存?
共享內存是GPU的一種稀缺資源,它位於芯片上,所以共享內存空間要比本地和全局內存空間快得多。對於warp裏的所有線程,只要線程之間沒有任何存儲體衝突(bank conflict),訪問共享內存就與訪問寄存器一樣快。
2)什麼是存儲體(bank)?
共享內存被劃分爲同樣大小的、可以同時訪問的內存塊,名爲存儲體。在計算能力爲1.x的設備上,存儲體數爲16,在2.0及以上的設備,存儲體數爲32。
存儲體的存在可使共享內存獲得高內存帶寬,假設有n個存儲體,此時訪問n個分別位於不同bank的地址時是同時進行的,最後獲得的有效帶寬就是單個模塊的帶寬的N倍,因爲只需要發射一次指令。
3)共享內存是如何映射到存儲體的?
共享內存空間的存儲體組織爲:連續的32位(4個字節)分配到連續的存儲體中,每個存儲體的帶寬爲 32位/2個時鐘週期
一維共享內存
如下的方式申請一個一維的共享內存 (32個存儲體)
__shared__ float sData[64];
// sData[0]-sData[31]分別對應bank[0]-bank[31];
// sData[32]-sData[63]分別對應bank[0]-bank[31];
此時,
sData[0]與sData[32]位於同一個存儲體bank[0]
sData[1]與sData[33]位於同一個存儲體bank[1]
。。。。。。
sData[31]與sData[63]位於同一個存儲體bank[31]
二維共享內存
二維共享內存其實可以展開成一維內存,其映射方式跟一維一樣。
__shared__ float sData[32][32];
由於上面共享內存的每一行大小爲32,剛好等於存儲體個數,那麼此時共享內存的每一列就是一個bank。
二、避免共享存儲體衝突(bank conflict)
同一個warp裏的線程訪問同一個bank裏不同的地址時,會出現bank conflict,如果訪問的不同的地址的個數爲n,那麼此種情況稱爲n路存儲器衝突(n-way bank conflict)。
1)同一個warp訪問共享內存的同一個bank的不同地址,所產生的bank conflict。
a.通過改變數據在共享內存中的排列方式,使其映射到bank時,原本在同一個bank的不同地址的數據分開到不同的bank。
二維共享內存例子:
size_t ix=threadIdx.x+blockIdx.x*blockDim.x;//0-31
size_t iy=threadIdx.y+blockIdx.y*blockDim.y;//0-31
__shared__ float sData[32][32+1];
//此處的共享內存改變了數據的排列方式,通過映射到bank上,使得原本在同一個bank的地址偏移到了不同的bank上。
if(ix<width&&iy<height)
{
sData[ix][iy]=Input_data[ix+iy*width];
__syncthreads();
Output[ix+iy*width]=sData[ix][iy];
}
一維共享內存例子:
__global__ void kernel1D(float *Input_data, float *Output_data,unsigned int length)
{
size_t tid = threadIdx.x;//0-63
size_t iy = tid >> (int)log2(32);//0-1
size_t ix = tid & (32 - 1);//0-31
__shared__ float sData[64 + 2];
float data;
if (tid < length)
{
sData[ix + iy * 33] = Input_data[tid];
__syncthreads();
data = sData[iy * 33];
}
}
b.如想對共享內存的列進行訪問,可讓列方向上的數據存入行方向上。例如:你想對矩陣的列進行操作,但是如果直接將矩陣一一對應映射到共享內存上,此時訪問列方向的數據時,會產生bank conflict。嘗試將矩陣轉置再進入核函數。
2)同一個warp的多條線程同時訪問同一個bank的同一地址,所產生的bank conflict
共享內存具有廣播機制,當處理一個內存讀取請求時,可以讀取一個32-位字並同時廣播到多個線程。當warp的多個線程從含有同一個32-位字的地址讀取時,這將減少存儲體衝突的數目。但前提是 該地址得爲廣播字
選擇哪個字作爲廣播字,以及在每個週期選擇哪個存儲體地址都不是特定的。所以,當只有一部分的線程去讀取同一個32-位字的地址時,需要我們自己選中廣播字
以下代碼會產生的bank conflict
//tid 爲0-63;下面代碼存在bank conflict
if ((tid&(32-1))<32)
{
Output_data[tid]=sData[tid];
__syncthreads();
}
else
{
Output_data[tid]=sData[63];
__syncthreads();
}
解決方法:先把sData[63]進行廣播
Output_data[tid]=sData[63];
if ((tid&(32-1))>32)
{
Output_data[tid]=sData[tid];
__syncthreads();
}
參考:
https://blog.csdn.net/endlch/article/details/47043069
https://blog.csdn.net/smsmn/article/details/6336060