基於MPI的矩陣相乘summa算法實現(附源程序)

      在科學與工程計算的許多問題中, 矩陣乘積是最基本的算法之一。在分佈存儲並行機上的經典矩陣乘積算法主要有1969年Cannon提出的二維mesh 上的矩陣乘積算法和1987年Fox等提出的“廣播-乘積-滾動”算法。 1994年Choi 等提出的PUMMA 算法將Fox 算法推廣到二維塊捲簾數據分佈上。同年,Huss-Lederman等又將Fox 算法推廣到虛二維環繞數據分佈。1995年van de Geijn 和Watts提出了一個簡單而高效的矩陣乘積算法, 稱爲SUMMA 算法。

      曙光4000並行機是國家智能計算機研究開發中心研製的一種基於分佈存儲結構和消息傳送機制的大規模並行計算機系統。本文討論了典型矩陣乘積算法SUMMA 算法在類似於曙光4000這樣的大規模並行機上的實現。在後續階段,作者將繼續分析並比較它和其他矩陣乘法並行算法的性能。

      SUMMA 算法首先將A , B 和C 劃分爲相同大小的矩陣,對應放在mesh_r × mesh_c 的二維mesh 上。 但SUMMA 算法將矩陣乘法分解爲一系列的秩nb 修正, 即各處理器中的A 和B 分別被分解爲nb 大小的列塊和行塊進行相乘,前面所說的分塊尺寸就是指nb 的大小。算法中, 廣播實現爲邏輯處理器行環或列環上的流水線傳送, 達到了計算與通信的重疊. 具體描述如算法1所示。

      算法1. 二維mesh 上的SUMMA 算法

C= 0
for i= 0 t o k-1 step nb do
 cur col = i×c/ n
 cur row = i×r / m
 if my col = cur rol 向本行廣播A 從i mod (k/c) 列開始的nb 列, 存於A′
 if my row = cur row 向本列廣播B 從i mod (k/r) 行開始的nb 行, 存於B ′
 C= A′×B ′
end for

      SUMMA算法的核心思想是:各處理器收集來自同一行處理器中A矩陣子塊的所有列和同一列處理器中B矩陣子塊的所有行,然後將行列相乘後累加,形成一個C矩陣的分塊矩陣。最後由rank=0的處理器將其他處理器的數據收集起來,形成最終的矩陣C。

      SUMMA算法相較於cannon算法的優勢只要體現在SUMMA算法能夠計算m*l的A矩陣和l*n的B矩陣相乘(m、l、n可不相等),而cannon算法只能實現n*n的A矩陣和n*n的B矩陣相乘,具有很大的侷限性。不過由於水平有限,並且爲了矩陣分塊比較好分,此MPI實現的SUMMA算法要求輸入的矩陣維數(m、l、n)必須爲處理器個數(由用戶自己定義)的整數倍,否則將提示錯誤。至於如何實現隨意維數的矩陣相乘,還要請各位偉大的程序猿多多指教。

      由於源碼太長,這裏只附上一部分MPI源碼,需要全部源碼的看客可郵件與我聯繫,聯繫方式在最下方。

/*summa算法 */
void My_summa(const int my_rank, int *a, int *b)
{
    int k, i, j;
    int *c;
    int len_c;
    int dest, tag;
    int my_rank_row, my_rank_col;
    int *a_col_send, *b_row_send, *a_col_recv, *b_row_recv;
    MPI_Status status_a, status_b;
    my_rank_row = my_rank / mesh_c;
    my_rank_col = my_rank % mesh_c;
    a_col_send = (int *)malloc((m / mesh_r) * sizeof(int));    
    b_row_send = (int *)malloc((n / mesh_c) * sizeof(int));    
    a_col_recv = (int *)malloc((m / mesh_r) * sizeof(int));
    b_row_recv = (int *)malloc((n / mesh_c) * sizeof(int));

        /*各處理器將自己所擁有的A矩陣分塊的列發送給跟自己同一行的其他處理器*/
    for (k = 0; k < l / mesh_c; ++k)
    {
        for (i = 0; i < m / mesh_r; ++i)
        {
            a_col_send[i] = a[i * (l / mesh_c) + k];
        }
        for (i = 0; i < mesh_c; ++i)
        {
            dest = my_rank_row * mesh_c + i;
            tag = my_rank_col * (l / mesh_c) + k;
            MPI_Send(a_col_send, m / mesh_r, MPI_INT, dest, tag, MPI_COMM_WORLD);
        }
    }
       /*各處理器將自己所擁有的B矩陣分塊的行發送給跟自己同一列的其他處理器*/
    for (k = 0; k < l / mesh_r; ++k)
    {                        
        for (i = 0; i < n / mesh_c; ++i)
        {
            b_row_send[i] = b[k * (n / mesh_c) + i];
        }
        for (i = 0; i < mesh_r; ++i)
        {
            dest = my_rank_col + i * mesh_c;
            tag = my_rank_row * (l / mesh_r) + k;
            MPI_Send(b_row_send, n / mesh_c, MPI_INT, dest, tag, MPI_COMM_WORLD);
        }
    }
       /*初始化C分塊 */
    len_c = (m / mesh_r) * (n / mesh_c);
    c = (int *)malloc(len_c * sizeof(int));
    for (i = 0; i < len_c; ++i)
    {
        c[i] = 0;
    }
       
    for (k = 0; k < l; ++k)
    {      /*各個處理器接收來自同一行處理器中的所有A分塊的列和同一列處理器中的所有B分塊的行*/
        MPI_Recv(a_col_recv, m / mesh_r, MPI_INT, my_rank_row * mesh_c + k / (l / mesh_c), k,
                MPI_COMM_WORLD, &status_a);
        MPI_Recv(b_row_recv, n / mesh_c, MPI_INT, k / (l / mesh_r) * mesh_c + my_rank_col, k,
                MPI_COMM_WORLD, &status_b);
               /*各個處理器已經擁有了計算對應C子塊的所有信息,相乘累加後即可得到對應C子塊*/
        for (i = 0; i < m / mesh_r; ++i)
        { 
            for (j = 0; j < n / mesh_c; ++j)
            {
                c[i * (n / mesh_c) + j] += a_col_recv[i] * b_row_recv[j]; 
            }
        }
    }
        /*各處理器將C子塊發送給0號進程*/
    MPI_Send(c, len_c, MPI_INT, 0, 99, MPI_COMM_WORLD);
    free(c);
    free(a_col_send), free(b_row_send), free(a_col_recv), free(b_row_recv);
}

 /*0號進程接收所有C子塊信息*/
void Collect_C()
{
    int k, i, j;
    int len_c, id_recv;
    int *c_recv;
    int c_imin, c_imax, c_jmin, c_jmax;
    MPI_Status status;

    len_c = (m / mesh_r) * (n / mesh_c);
    c_recv = (int *)malloc(len_c * sizeof(int));
        /*將C子塊填到相應的C矩陣座標下 */
for (k = 0; k < num_procs; ++k)
    {
        MPI_Recv(c_recv, len_c, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
        id_recv = status.MPI_SOURCE;
        c_imin = (id_recv / mesh_c) * (m / mesh_r);
        c_imax = c_imin + m / mesh_r - 1;
        c_jmin = (id_recv % mesh_c) * (n / mesh_c);
        c_jmax = c_jmin + n / mesh_c - 1;
        for (i = c_imin; i <= c_imax; ++i)
        {
            for (j = c_jmin; j <= c_jmax; ++j)
            {
                C[i][j] = c_recv[(i - c_imin) * (n / mesh_c) + (j - c_jmin)];
            }
        }    
    }

    free(c_recv);
}

 /*打印矩陣 */
void Print(char *str, int **matrix, int m, int n)
{
    int i, j;
    printf("%s", str);
    for (i = 0; i < m; ++i)
    {
        for (j = 0; j < n; ++j)
        {
            printf("%-6d", matrix[i][j]);
        }
        printf("\n");
    }
    printf("\n");
}

 編譯: mpicc summa.c -o summa -lm       注:如出現p0_18495:  p4_error: Child process exited while making connection to remote process on node2: 0
                                                                           Killed by signal 2.  這種錯誤,解決辦法:在主目錄下直接輸入:source /home/guest/.bashrc
 運行: mpirun -np 9 summa 9 6 12

 運行結果:

Allocate_A_B_C cheers!
Random_A_B cheers!
Scatter_A_B cheers!
Collect_C cheers!
random matrix A:
54    9     90    76    54    45    
52    70    1     99    64    31    
40    34    34    31    7     16    
82    26    18    70    29    44    
64    58    29    71    98    89    
42    5     50    84    81    4     
29    86    26    83    85    42    
66    77    76    0     8     35    
69    91    13    87    13    43    

random matrix B:
83    77    1     12    0     51    53    94    56    3     78    90    
60    8     28    38    43    65    81    9     42    9     61    50    
97    30    41    10    17    54    53    52    84    6     17    84    
58    70    79    66    26    57    8     38    17    36    76    60    
1     9     21    43    19    83    94    16    13    87    78    83    
94    32    35    78    90    4     62    48    75    93    67    53    

Matrix C = A * B:
22444 14176 12709 12738 8969  17193 16835 15749 16331 12402 19294 24297 
17333 13092 12303 14998 9607  18335 17209 11844 10776 12807 22936 21159 
11967 7117  5542  5707  4419  8498  8574  7892  8342  3843  9746  11445 
18337 13631 9227  11451 7755  13417 13420 14114 12063 9723  18818 19131 
24187 14962 13659 19104 14705 21137 24925 16584 17612 20247 28026 28207 
13965 11511 10709 10533 5148  16694 13815 11273 9543  10914 17401 20205 
18936 11620 13315 16285 11693 20427 21139 11382 13086 15306 23702 23355 
20768 9170  6731  7552  7905  13279 16685 12657 16043 5298  14106 18693 
21549 14014 11801 14071 10513 16346 16301 13559 13651 9366  21661 20430 

Print cheers!
successful run time is 0.109375

 

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