1. 程式人生 > >矩陣乘法的平行計算

矩陣乘法的平行計算

設兩個矩陣A和B,大小分別為M * N 和 N * P, 如果C = A * B, 則C的大小為M * P。

矩陣演算法的演算法表示,虛擬碼如下:

  1. for (i = 0; i < M; ++i){  
  2.     for (j = 0; j < P; ++j){  
  3.         C[i][j] = 0;  
  4.         for (k = 0; k < N; ++k){  
  5.             C[i][j] += A[i][k] * B[k][j];  
  6.         }  
  7.     }  
  8. }  

從上面的演算法中可以看出該演算法的時間複雜度為O(M*N*P),當M,N,P都非常大時該計算將非常耗時。那麼如何將上面的序列演算法轉換成並行演算法呢?

從上面的三層迴圈中可以看出最外層的迴圈是獨立的,即對C[i][*]的計算不依賴於任何C[ii][*]的計算,因此我們可以非常容易將最外層的迴圈轉換成並行。

  1. #prama omp parallel for num_threads(CORE_NUM)
  2. for (i = 0; i < M; ++i){  
  3.     for (j = 0; j < P; ++j){  
  4.         C[i][j] = 0;  
  5.         for (k = 0; k < N; ++k){  
  6.             C[i][j] += A[i][k] * B[k][j];  
  7.         }  
  8.     }  
  9. }  

但是這裡有一個侷限,如果假設cpu的核數CORE_NUM > M,同樣無法充分利用所有的計算資源。

進一步分析, 由於C矩陣的大小為M * P,那麼我們能不能將C的計算下平均分配到CORE_NUM個核心上呢,即每個核分配ceil(M*P/CORE_NUM)個計算任何,即將上面的第一和第二層並行化。

首先將C轉換成一維的陣列T[M*P] , 則C[i][j] = T[i * M + j], 反過來T[z] = C[z/M] [ z %P]。

故進一步的並行演算法為:

  1. #prama omp parallel for num_threads(NUM)
  2. for
     (z = 0; z < M * P; ++z){  
  3.         i = z / P;  
  4.         j = z % P;  
  5.         C[i][j] = 0;  
  6.         for (k = 0; k < N; ++k){  
  7.             C[i][j] += A[i][k] * B[k][j];  
  8.     }  
  9. }  

效能優化。

看最裡面一層的計算

  1. for (k = 0; k < N; ++k){  
  2.     C[i][j] += A[i][k] * B[k][j];  
由於記憶體中二維陣列是以行優先進行儲存的,因此B[k][j]存在嚴重的cache命中率問題,解決這個問題的方法是也將B進行一次沿對角線進行翻轉,使得最裡面的計算變成
  1. for (k = 0; k < N; ++k){  
  2.     C[i][j] += A[i][k] * B[j][k];  

另外一點需要注意的就是C[i][j] += A[i][k] * B[j][k];計算時的偽共享問題。