CUDA矩陣乘法——利用共享存儲器


上篇的方法是在全局存儲區中,這樣對取數據時速度回很慢,影響性能,而設備中線程對塊中的共享存儲區中數據讀取時速度是很快的,並且在全局存儲區中進行讀取時,有很多數組元素的重復讀取。因此,先將需要計算的數組數據讀取到共享存儲區中,再利用共享存儲區中的數據進行計算,就會提高性能

但由於每個塊的共享存儲區的存儲空間一般很小,以本人8400MG為例,只有16KB,因此在一個塊內需要的數據量大時,有必要對數據進行分塊,分塊進行計算。

1.分塊策略

比如上篇中的:

網格維度:(width/TILE_WIDTH,width/TILE_WIDTH)     (64,64)

塊維度:(TILE_WIDTH,TILE_WIDTH)    (16,16)

計算如圖中Pd中的一個塊需要Md和Nd虛線包圍的數據。Pd一個塊中的所需的數據大小:TILE_WIDTH*width*2*4/1024  (KB)   2因為Md和Nd兩個數組,4因為float。計算得128MB。顯然大大超過了16KB,這時就需要采用分塊的方式計算。(注意:分塊計算的方式在大數據處理中是很常用的方法)

分塊大小:可以先嘗試嘗試,就以TILE_WIDTH*TILE_WIDTH的矩形塊為一小塊,進行分塊分階段計算。計算需要的存儲器大小為16*16*2*4/1024=2KB,可以,根據上圖也很清晰,思路明了(這是關鍵)。

2.源程序

__shared__關鍵字說明變量存儲在共享存儲區內,共享存儲區內的數據對塊內的線程是共享的。在線程內申明的變量作用范圍知識線程。

每個塊中含有TILE_WIDTH*TILE_WIDTH個線程,而每個小塊中Md和Nd中的數據元素為TILE_WIDTH*TILE_WIDTH,因此塊中的一個線程將Md和Nd中的一個元素加載進共享存儲器。如下代碼第21、22行所示(tx,ty)處的線程分別將Md和Nd中的一個元素加載進共享存儲器中。

采用此方法,kernel函數就要改變,如下:

 1 __global__ static void MatrixMulKernel(const float* Md,const float* Nd,float* Pd,int Width)
 2 {
 3     //共享存儲器保存從從全局存儲器中加載的數據
 4     __shared__ float Mds[TILE_WIDTH][TILE_WIDTH];
 5     __shared__ float Nds[TILE_WIDTH][TILE_WIDTH];
 6 
 7     //計算Pd和Md中元素的行索引
 8     int bx=blockIdx.x;
 9     int by=blockIdx.y;
10     int tx=threadIdx.x;
11     int ty=threadIdx.y;
12     //Pd的行和列
13     int Row = by*TILE_WIDTH+ty; 
14     int Col = bx*TILE_WIDTH+tx; 
15 
16     float Pvalue = 0.0;
17     //第k小塊
18     for (int k=0;k<Width/TILE_WIDTH;k++)
19     {
20         //通過協作把Md和Nd的塊加載到共享存儲器中
21         Mds[ty][tx]=Md[Row*Width+k*TILE_WIDTH+tx];
22         Nds[ty][tx]=Nd[(k*TILE_WIDTH+ty)*Width+Col];
23         __syncthreads(); //等待塊中其他線程同步
24 
25         for(int m=0;m<TILE_WIDTH;m++)
26             Pvalue +=Mds[ty][m]*Nds[m][tx];
27         __syncthreads(); //等待其他線程計算完,因為Pvalue要用到下一個塊的計算
28     }
29     //每個線程負責計算P中的一個元素
30     Pd[Row*Width+Col]=Pvalue;
31 }

里面比較重要的是線程同步,用__syncthreads()函數,完成一個塊內線程的同步的功能,因為要讓線程將需要的數據都加在進共享存儲區內才能進行計算。

3.測試結果

CPU計算比較耗時,就不測試了,可以看到采用此方法比上篇中的性能要提高了不少。

4.不同分配策略比較

上面只是用到了2KB的共享存儲器,可以考慮將分塊數據更大一點,即將上面Md和Nd中四個小塊合並為一個小塊

Md一個小塊大小:(TILE_WIDTH,TILE_WIDTH*4)

Nd一個小塊的大小:(TILE_WIDTH*4,TILE_WIDTH)

上面代碼就要進行些變化,主要是共享存儲器分配大小以及循環變量的循環條件。

測試結果如下:

可見比前面要慢些。這里還可以利用其它分塊策略進行測試,從而找到最適合的分塊策略,分塊是同時也要兼顧物理存儲限制及存儲器訪問模型。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM