CUDA——性能優化之共享內存

一、共享內存的結構

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章