這里的內容主要是都志輝老師《高性能計算之並行編程技術——MPI並行程序設計》
書上有一些代碼是FORTAN的,我在學習的過程中,將其都轉換成C的代碼,便於統一記錄。
這章內容分為兩個部分:MPI對等模式程序例子 & MPI主從模式程序例子
1. 對等模式MPI程序設計
1.1 問題背景
這部分以Jacobi迭代為具體問題,列舉了三個求解Jacobi迭代問題的MPI對等模式程序。
這里需要闡明一下,書上的Jacobi迭代具體的背景可以參考這個內容:http://www.mcs.anl.gov/research/projects/mpi/tutorial/mpiexmpl/src/jacobi/C/main.html
簡答說,就是矩陣每個元素都是其上下左右四個元素的平均值,矩陣四個邊界的值不變。
這里學習的重點暫時放在如何完成上述矩陣的迭代的功能實現,不應該偏離過多去糾結Jacobi迭代,提高專注度。
1.2 第一版最原始的Jacobi迭代對等程序
代碼如下:
1 #include "mpi.h" 2 #include <stdio.h> 3 #include <stdlib.h> 4 5 #define N 8 6 #define SIZE N/4 7 #define T 2 8 9 10 void print_myRows(int, float [][N]); 11 12 int main(int argc, char *argv[]) 13 { 14 float myRows[SIZE+2][N], myRows2[SIZE+2][N]; 15 int myid; 16 MPI_Status status; 17 18 MPI_Init(&argc, &argv); 19 MPI_Comm_rank(MPI_COMM_WORLD, &myid); 20 21 int i,j; 22 /*初始化*/ 23 for ( i = 0; i<SIZE+2; i++) 24 { 25 for ( j = 0; j<N; j++) 26 { 27 myRows[i][j] = myRows2[i][j] = 0; 28 } 29 } 30 if ( 0 == myid) { 31 for ( j = 0; j<N; j++) 32 myRows[1][j] = 8.0; 33 } 34 if (3 == myid) { 35 for ( j=0; j<N; j++) 36 myRows[SIZE][j] = 8.0; 37 } 38 for ( i = 1; i<SIZE+1; i++) 39 { 40 myRows[i][0] = 8.0; 41 myRows[i][N-1] = 8.0; 42 } 43 /*Jacobi Iteration部分*/ 44 int step; 45 for ( step = 0; step < T; step++ ) 46 { 47 // 傳遞數據 48 if (myid<3) { 49 // 從 下方 進程接收數據 50 MPI_Recv(&myRows[SIZE+1][0], N, MPI_FLOAT, myid+1, 0, MPI_COMM_WORLD, &status); 51 } 52 if (myid>0) { 53 // 向 上方 進程發送數據 54 MPI_Send(&myRows[1][0], N, MPI_FLOAT, myid-1, 0, MPI_COMM_WORLD); 55 } 56 if (myid<3) { 57 // 向 下方 進程發送數據 58 MPI_Send(&myRows[SIZE][0], N, MPI_FLOAT, myid+1, 0, MPI_COMM_WORLD); 59 } 60 if (myid>0) { 61 // 從 上方 進程接收數據 62 MPI_Recv(&myRows[0][0], N, MPI_FLOAT, myid-1, 0, MPI_COMM_WORLD, &status); 63 } 64 // 計算 65 int r_begin, r_end; 66 r_begin = (0==myid) ? 2 : 1; 67 r_end = (3==myid) ? SIZE-1 : SIZE; 68 for ( i = r_begin; i<=r_end; i++) 69 { 70 for ( j = 1; j<N-1; j++) 71 myRows2[i][j] = 0.25*(myRows[i][j-1]+myRows[i][j+1]+myRows[i-1][j]+myRows[i+1][j]); 72 } 73 // 更新 74 for ( i = r_begin; i<=r_end; i++) 75 { 76 for (j = 1; j<N-1; j++) 77 myRows[i][j] = myRows2[i][j]; 78 } 79 } 80 // MPI_Barrier(MPI_COMM_WORLD); 81 print_myRows(myid, myRows); 82 MPI_Finalize(); 83 } 84 85 void print_myRows(int myid, float myRows[][N]) 86 { 87 int i,j; 88 int buf[1]; 89 MPI_Status status; 90 buf[0] = 1; 91 if ( myid>0 ) { 92 MPI_Recv(buf, 1, MPI_INT, myid-1, 0, MPI_COMM_WORLD, &status); 93 } 94 printf("Result in process %d:\n", myid); 95 for ( i = 0; i<SIZE+2; i++) 96 { 97 for ( j = 0; j<N; j++) 98 printf("%1.3f\t", myRows[i][j]); 99 printf("\n"); 100 } 101 if ( myid<3 ) { 102 MPI_Send(buf, 1, MPI_INT, myid+1, 0, MPI_COMM_WORLD); 103 } 104 MPI_Barrier(MPI_COMM_WORLD); 105 }
代碼執行結果:
代碼分析:
(1)矩陣分塊方式
先說明一下,這與原書上的代碼設計思路有差別:都老師的書上是把矩陣按columns划分的,我上面這份代碼是將矩陣按照row分塊的。
書上為什么要按照列進行矩陣分塊呢?我覺得原因是FORTAN的二維數組是按照列來優先存儲元素的,所以按列划分矩陣,對於后面的MPI_Send MPI_Recv操作都比較方便。
而我使用的是C語言來實現,C語言的二維數組是優先按照行來存儲的,所以按照行來划分矩陣更划算,對於后面的MPI_Recv和MPI_Send操作更方便。
***關於矩陣分塊的方式與矩陣乘法***
有關這個部分,我查閱了一本《高性能並行計算》的講義:http://www.sccas.cn/yhfw/wdypx/wd/lszl/201112/W020111215333260773702.pdf
參閱了這本講義的第3.1節內容,矩陣A×矩陣B並行計算的四種矩陣划分方式:行行、行列、列行、列列
這幾種方法的核心就是:固定A或B,移動一個;或都固定,移動中間結果。
如果忘記了算法是如何移動數據的,通過畫圖的方式可以幫助理解:在每個時間片內,畫出各個處理機有哪些數據,每個處理機內部進行了哪些運算。
另外,這些算法的效率比較主要考察“數據交換量”和“計算量”兩個指標。
(2)代碼設計邏輯
這個算法並不復雜,比較重要的是如何在對等模式下,各個進程要互相發送和接受數據。如何設計通信次序才能保證進程之間沒有死鎖出現呢?
關於這個問題,可以回顧一下都老師這本書上“7.7編寫安全的MPI程序”。
我的理解就是:如果進程A和進程B需要互相發送和接收數據,畫出進程之間交換數據的調用次序圖,如果次序圖中沒有兩個箭頭是交叉的,那么就認為不會出現通信死鎖。
上面這個程序,通信調用次序圖如下:
可以看到,上述的通信調用次序圖中,沒有兩個箭頭是交叉的,所以是安全的。
同時,也可以看到上面的程序關於進程通信的部分跟計算部分是完全分開的:
通信部分只需要傳遞少量邊界數據,計算部分充分利用各個處理器的並行計算優勢。
1.3 捆綁發送接受的Jacobi迭代實現
代碼實現:
1 #include "mpi.h" 2 #include <stdio.h> 3 #include <stdlib.h> 4 5 #define N 8 6 #define SIZE N/4 7 #define T 2 8 9 10 void print_myRows(int, float [][N]); 11 12 int main(int argc, char *argv[]) 13 { 14 float myRows[SIZE+2][N], myRows2[SIZE+2][N]; 15 int myid; 16 MPI_Status status; 17 18 MPI_Init(&argc, &argv); 19 MPI_Comm_rank(MPI_COMM_WORLD, &myid); 20 21 int i,j; 22 /*初始化*/ 23 for ( i = 0; i<SIZE+2; i++) 24 { 25 for ( j = 0; j<N; j++) 26 { 27 myRows[i][j] = myRows2[i][j] = 0; 28 } 29 } 30 if ( 0 == myid) { 31 for ( j = 0; j<N; j++) 32 myRows[1][j] = 8.0; 33 } 34 if (3 == myid) { 35 for ( j=0; j<N; j++) 36 myRows[SIZE][j] = 8.0; 37 } 38 for ( i = 1; i<SIZE+1; i++) 39 { 40 myRows[i][0] = 8.0; 41 myRows[i][N-1] = 8.0; 42 } 43 /*Jacobi Iteration部分*/ 44 int step; 45 for ( step = 0; step < T; step++ ) 46 { 47 // 從上往下平移數據 48 if ( 0 == myid) { 49 MPI_Send(&myRows[SIZE][0], N, MPI_FLOAT, myid+1, 0, MPI_COMM_WORLD); 50 } 51 else if (3 == myid) { 52 MPI_Recv(&myRows[0][0], N, MPI_FLOAT, myid-1, 0, MPI_COMM_WORLD, &status); 53 } 54 else { 55 MPI_Sendrecv(&myRows[SIZE][0], N, MPI_FLOAT, myid+1, 0, &myRows[0][0], N, MPI_FLOAT, myid-1, 0, MPI_COMM_WORLD, &status); 56 } 57 // 從下向上平移數據 58 if (3 == myid) { 59 MPI_Send(&myRows[1][0], N, MPI_FLOAT, myid-1, 0, MPI_COMM_WORLD); 60 } 61 else if (0 == myid) { 62 MPI_Recv(&myRows[SIZE+1][0], N, MPI_FLOAT, myid+1, 0, MPI_COMM_WORLD, &status); 63 } 64 else { 65 MPI_Sendrecv(&myRows[1][0], N, MPI_FLOAT, myid-1, 0, &myRows[SIZE+1][0], N, MPI_FLOAT, myid+1, 0, MPI_COMM_WORLD, &status); 66 } 67 // 計算 68 int r_begin, r_end; 69 r_begin = (0==myid) ? 2 : 1; 70 r_end = (3==myid) ? SIZE-1 : SIZE; 71 for ( i = r_begin; i<=r_end; i++) 72 { 73 for ( j = 1; j<N-1; j++) 74 myRows2[i][j] = 0.25*(myRows[i][j-1]+myRows[i][j+1]+myRows[i-1][j]+myRows[i+1][j]); 75 } 76 // 更新 77 for ( i = r_begin; i<=r_end; i++) 78 { 79 for (j = 1; j<N-1; j++) 80 myRows[i][j] = myRows2[i][j]; 81 } 82 } 83 MPI_Barrier(MPI_COMM_WORLD); 84 print_myRows(myid, myRows); 85 MPI_Finalize(); 86 } 87 88 void print_myRows(int myid, float myRows[][N]) 89 { 90 int i,j; 91 int buf[1]; 92 MPI_Status status; 93 buf[0] = 1; 94 if ( myid>0 ) { 95 MPI_Recv(buf, 1, MPI_INT, myid-1, 0, MPI_COMM_WORLD, &status); 96 } 97 printf("Result in process %d:\n", myid); 98 for ( i = 0; i<SIZE+2; i++) 99 { 100 for ( j = 0; j<N; j++) 101 printf("%1.3f\t", myRows[i][j]); 102 printf("\n"); 103 } 104 if ( myid<3 ) { 105 MPI_Send(buf, 1, MPI_INT, myid+1, 0, MPI_COMM_WORLD); 106 } 107 MPI_Barrier(MPI_COMM_WORLD); 108 }
代碼分析:
如果一個進程既發送數據又接收數據,則可以使用MPI_Sendrecv函數來實現。
比如Jacobi迭代中的中間兩個進程,都需要發送和接收。這樣處理起來就可以用MPI_Sendrecv函數。
上述代碼量並沒有減少,但是設計邏輯稍微直觀了一些。
1.4 引入虛擬進程的Jacobi迭代
代碼實現:
1 #include "mpi.h" 2 #include <stdio.h> 3 #include <stdlib.h> 4 5 #define N 8 6 #define SIZE N/4 7 #define T 2 8 9 10 void print_myRows(int, float [][N]); 11 12 int main(int argc, char *argv[]) 13 { 14 float myRows[SIZE+2][N], myRows2[SIZE+2][N]; 15 int myid; 16 MPI_Status status; 17 18 MPI_Init(&argc, &argv); 19 MPI_Comm_rank(MPI_COMM_WORLD, &myid); 20 21 int i,j; 22 /*初始化*/ 23 for ( i = 0; i<SIZE+2; i++) 24 { 25 for ( j = 0; j<N; j++) 26 { 27 myRows[i][j] = myRows2[i][j] = 0; 28 } 29 } 30 if ( 0 == myid) { 31 for ( j = 0; j<N; j++) 32 myRows[1][j] = 8.0; 33 } 34 if (3 == myid) { 35 for ( j=0; j<N; j++) 36 myRows[SIZE][j] = 8.0; 37 } 38 for ( i = 1; i<SIZE+1; i++) 39 { 40 myRows[i][0] = 8.0; 41 myRows[i][N-1] = 8.0; 42 } 43 /*Jacobi Iteration部分*/ 44 int upid, downid; 45 upid = (0==myid) ? MPI_PROC_NULL : myid-1; 46 downid = (3==myid) ? MPI_PROC_NULL : myid+1; 47 int step; 48 for ( step = 0; step < T; step++ ) 49 { 50 /*從上向下平移數據*/ 51 MPI_Sendrecv(&myRows[SIZE][0], N, MPI_FLOAT, downid, 0, &myRows[0][0], N, MPI_FLOAT, upid, 0, MPI_COMM_WORLD, &status); 52 /*從下往上發送數據*/ 53 MPI_Sendrecv(&myRows[1][0], N, MPI_FLOAT, upid, 0, &myRows[SIZE+1][0], N, MPI_FLOAT, downid, 0, MPI_COMM_WORLD, &status); 54 // 計算 55 int r_begin, r_end; 56 r_begin = (0==myid) ? 2 : 1; 57 r_end = (3==myid) ? SIZE-1 : SIZE; 58 for ( i = r_begin; i<=r_end; i++) 59 { 60 for ( j = 1; j<N-1; j++) 61 myRows2[i][j] = 0.25*(myRows[i][j-1]+myRows[i][j+1]+myRows[i-1][j]+myRows[i+1][j]); 62 } 63 // 更新 64 for ( i = r_begin; i<=r_end; i++) 65 { 66 for (j = 1; j<N-1; j++) 67 myRows[i][j] = myRows2[i][j]; 68 } 69 } 70 MPI_Barrier(MPI_COMM_WORLD); 71 print_myRows(myid, myRows); 72 MPI_Finalize(); 73 } 74 75 void print_myRows(int myid, float myRows[][N]) 76 { 77 int i,j; 78 int buf[1]; 79 MPI_Status status; 80 buf[0] = 1; 81 if ( myid>0 ) { 82 MPI_Recv(buf, 1, MPI_INT, myid-1, 0, MPI_COMM_WORLD, &status); 83 } 84 printf("Result in process %d:\n", myid); 85 for ( i = 0; i<SIZE+2; i++) 86 { 87 for ( j = 0; j<N; j++) 88 printf("%1.3f\t", myRows[i][j]); 89 printf("\n"); 90 } 91 if ( myid<3 ) { 92 MPI_Send(buf, 1, MPI_INT, myid+1, 0, MPI_COMM_WORLD); 93 } 94 MPI_Barrier(MPI_COMM_WORLD); 95 }
代碼分析:
Jacobi迭代的麻煩之處在於(按行划分矩陣):最上面的矩陣只需要與它下面的矩陣所在進程互相通信一次,最下面的矩陣只需要與它上面的矩陣所在進程互相通信一次,而中間的其余矩陣都需要與其上下相鄰的矩陣所在進程通信兩次。
因此,必須對最上面和最下面的矩陣特殊處理,作為一種corner case來對待,所以代碼麻煩。
這里引入了MPI_PROC_NULL虛擬進程的概念,相當於給最上面的矩陣之上再來一個想象存在的進程:與這個虛擬進程通信不會真的通信,而是立刻返回。
這個虛擬進程的意義在於可以方便處理corner case,如上面的例子,無論是代碼量和設計思路都簡化了許多,MPI替我們完成了很多工作。
2. 主從模式MPI程序設計
2.1 矩陣A×向量B=向量C
主要通過矩陣A×向量B來講解主從模式的程序設計,這個程序也是從FORTAN翻譯成C代碼的。
大體思路如下:
(1)一個master進程,負責總體調度,廣播向量B到slaver進程,並把矩陣A的每一行發送到某個slaver進程
(2)slaver一開始從master的廣播中獲得向量B,之后每次從master獲得矩陣A的某一行(具體的行號,利用MPI_TAG發送;但是為了要把0行空出來作為結束標志,所以從1開始),計算矩陣A的該行與向量B的內積后再回傳給master進程
(3)master進程每次從一個slaver進程獲得向量C的某個元素的值,master進程通過MPI_Recv中的status.MPI_TAG來判斷該計算結果該更新到向量C的哪個位置中
(4)如果master進程從某個slaver回收計算結果后,沒有新的計算任務要派送給slaver進程了,就向slaver進程發送一個MPI_TAG=0的消息;slaver收到MPI_TAG=0的消息,就結束退出
具體代碼實現如下:
1 #include "mpi.h" 2 #include <stdio.h> 3 4 #define ROWS 100 5 #define COLS 100 6 #define min(x,y) ((x)>(y)?(y):(x)) 7 int main(int argc, char *argv[]) 8 { 9 int rows = 10, cols = 10; 10 int master = 0; 11 int myid, numprocs; 12 int i,j; 13 float a[ROWS][COLS], b[COLS], c[COLS]; 14 float row_result; 15 MPI_Status status; 16 17 MPI_Init(&argc, &argv); 18 MPI_Comm_rank(MPI_COMM_WORLD, &myid); 19 MPI_Comm_size(MPI_COMM_WORLD, &numprocs); 20 21 /*master進程*/ 22 if (master == myid) { 23 /*初始化矩陣a和b*/ 24 for (j=0; j<cols; j++) b[j]=1; 25 for (i=0; i<rows; i++) 26 { 27 for (j=0; j<cols; j++) 28 { 29 a[i][j] = i; 30 } 31 } 32 /*只在master進程中初始化b 其余slave進程通過被廣播的形式獲得向量b*/ 33 MPI_Bcast(&b[0], cols, MPI_FLOAT, master, MPI_COMM_WORLD); 34 /*向各個slave進程發送矩陣a的各行*/ 35 int numsent = 0; 36 for ( i=1; i<min(numprocs,rows+1); i++) 37 { 38 /* 每個slave進程計算一行×一列的結果 這里用MPI_TAG參數標示對應結果向量c的下標+1 39 * MPI_TAG在這里的開始取值范圍是1 要把MPI_TAG空出來 作為結束slave進程的標志*/ 40 MPI_Send(&a[i-1][0], cols, MPI_FLOAT, i, ++numsent, MPI_COMM_WORLD); 41 } 42 /*master進程接受其他各進程的計算結果*/ 43 for ( i=0; i<rows; i++) 44 { 45 /*類似poll的方法 只要有某個slave進程算出來結果了 MPI_Recv就能返回執行一次*/ 46 MPI_Recv(&row_result, 1, MPI_FLOAT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status); 47 /*這里MPI_TAG的范圍是1到rows 注意存儲結果時下標減1*/ 48 c[status.MPI_TAG-1] = row_result; 49 /*發送矩陣a中沒發送完的行 就用剛返回計算結果空出來的那個slave進程 通過status.MPI_SOURCE找到這個空出來的進程*/ 50 if (numsent < rows) { 51 MPI_Send(&a[numsent][0], cols, MPI_FLOAT, status.MPI_SOURCE, numsent+1, MPI_COMM_WORLD); 52 numsent = numsent + 1; 53 } 54 else { /*發送空消息 關閉slave進程*/ 55 float close = 1.0; 56 MPI_Send(&close, 1, MPI_FLOAT, status.MPI_SOURCE, 0, MPI_COMM_WORLD); 57 } 58 } 59 /*打印乘法結果*/ 60 for (j = 0; j < cols; j++ ) 61 printf("%1.3f\t", c[j]); 62 printf("\n"); 63 } 64 /*slave進程*/ 65 else { 66 MPI_Bcast(&b[0], cols, MPI_FLOAT, master, MPI_COMM_WORLD); 67 while(1) 68 { 69 row_result = 0; 70 MPI_Recv(&c[0], cols, MPI_FLOAT, master, MPI_ANY_TAG, MPI_COMM_WORLD, &status); 71 if ( 0 != status.MPI_TAG ) { 72 for ( j = 0; j < cols; j++ ) 73 { 74 row_result = row_result + b[j]*c[j]; 75 } 76 //printf("myid:%d, MPI_TAG:%d, c[0]:%f, row_result:%1.3f\n", myid, status.MPI_TAG,c[0], row_result); 77 MPI_Send(&row_result, 1, MPI_FLOAT, master, status.MPI_TAG, MPI_COMM_WORLD); 78 } 79 else { 80 break; 81 } 82 } 83 } 84 MPI_Finalize(); 85 }
代碼執行結果:
這里有兩個細節需要注意:
(1)關於++運算符在傳遞參數時的使用
代碼40行一開始我寫成了“MPI_Send(&a[numsent][0], cols, MPI_FLOAT, i, ++numsent, MPI_COMM_WORLD);”
顯然,這個代碼是錯誤的,問題的關鍵就出在了numsent這個變量上。
比如,在執行這個語句前numsent的值為1。我希望第一個參數傳遞的是&a[1][0],第四個參數是2,並且numsent此時的值為2。
這是一個思維陷阱,++numsent會導致numsent先加1,再作為值傳遞到MPI_Send實參中。所以,此時第一個參數變成了&a[2][0],並不是原先想要的。
為了避免這種思維陷阱,以后再設計傳遞函數的參數時,應該禁止在傳遞參數時使用++這個運算符;多寫一個賦值語句不會怎樣,反之用++運算符就容易掉進思維陷阱。
(2)關於#define帶參宏定義的使用
代碼中用到#define min(x,y) ((x)>(y)?(y):(x))這個宏定義,這個地方稍微吃了一點兒坑。
記住兩個括號的原則:
a. 每個變量都要加括號
b. 宏定義整體要加括號
如果想看看宏定義是否替換問正確的內容了,可以cc -E選項來查看預處理后的結果。
2.2 master進程打印各個slaver進程發送來的消息
代碼實現:
1 #include "mpi.h" 2 #include <stdio.h> 3 #include <string.h> 4 5 #define MSG_EXIT 1 6 #define MSG_ORDERED 2 7 #define MSG_UNORDERED 3 8 9 10 void master_io(); 11 void slaver_io(); 12 13 int main(int argc, char *argv[]) 14 { 15 int rank, size; 16 MPI_Init(&argc, &argv); 17 MPI_Comm_rank(MPI_COMM_WORLD, &rank); 18 if (0 == rank) { 19 master_io(); 20 } 21 else { 22 slaver_io(); 23 } 24 MPI_Finalize(); 25 } 26 27 void master_io() 28 { 29 int i,j,size,nslaver,firstmsg; 30 char buf[256], buf2[256]; 31 MPI_Status status; 32 MPI_Comm_size(MPI_COMM_WORLD, &size); 33 nslaver = size - 1; 34 while (nslaver>0) { 35 MPI_Recv(buf, 256, MPI_CHAR, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status); 36 switch(status.MPI_TAG){ 37 case MSG_EXIT: 38 nslaver--; 39 break; 40 case MSG_UNORDERED: // 如果是亂序就直接輸出 41 fputs(buf, stdout); 42 break; 43 case MSG_ORDERED: 44 firstmsg = status.MPI_SOURCE; 45 /* 這段程序設計的比較巧妙 46 * 雖然每個slaver發送兩個message 不同slaver的message可能有重疊 47 * 但是每個每個slaver每次只能被接收一個message 48 * 這一輪一旦接收到message了 49 * 就不再從這個slaver接收消息了 50 * 概括說: 51 * 第一次進入MSG_ORDERED處理各個slaver第一次調用MPI_Send發送的消息 52 * 第二次進入MSG_ORDERED處理各個slaver第二次調用MPI_Send發送的消息*/ 53 for ( i=1; i<size; i++) 54 { 55 if (i==firstmsg) { 56 fputs(buf, stdout); 57 } 58 else { 59 MPI_Recv(buf2, 256, MPI_CHAR, i, MSG_ORDERED, MPI_COMM_WORLD, &status); 60 fputs(buf2, stdout); 61 } 62 } 63 break; 64 } 65 } 66 } 67 68 void slaver_io() 69 { 70 char buf[256]; 71 int rank; 72 MPI_Comm_rank(MPI_COMM_WORLD, &rank); 73 /*第一次向master進程發送有序消息*/ 74 sprintf(buf, "Hello from slaver %d, ordered print\n", rank); 75 MPI_Send(buf, strlen(buf)+1, MPI_CHAR, 0, MSG_ORDERED, MPI_COMM_WORLD); 76 /*第二次向master進程發送有序消息*/ 77 sprintf(buf, "Bye from slaver %d, ordered print\n", rank); 78 MPI_Send(buf, strlen(buf)+1, MPI_CHAR, 0, MSG_ORDERED, MPI_COMM_WORLD); 79 /*第一次向master發送無序消息*/ 80 sprintf(buf, "I'm exiting (%d), unordered print\n", rank); 81 MPI_Send(buf, strlen(buf)+1, MPI_CHAR, 0, MSG_UNORDERED, MPI_COMM_WORLD); 82 /*發送結束信息*/ 83 MPI_Send(buf, 0, MPI_CHAR, 0, MSG_EXIT, MPI_COMM_WORLD); 84 }
有一個不錯的小巧的設計思路,在代碼注釋中具體說明了,這里不再贅述。
把這個例子看明白了,非常有利於理解master slaver的設計模式,並且知道如何花式保證每次master都能接受發送自slaver相同“批次”的消息。
小結:
看完了這章內容,對MPI程序設計稍微有些感覺了。另外,感覺前面擼了一遍APUE的幫助挺大的,雖然MPI程序接口比較簡單,但是總讓我想到APUE中的fork+IPC的部分。