並行歸並排序在程序開始時,會將n/comm_comm個鍵值分配給每個進程,程序結束時,所有的鍵值會按順序存儲在進程0中。為了做到這點,它使用了樹形結構通信模式。當進程接收到另一個進程的鍵值時,它將該鍵值合並進自己排序的鍵值列表中。編寫一個程序實現歸並排序。進程0應該讀入n的值,將其廣播給其余進程。每個進程需要使用隨機數生成器來創建n/comm_sz的局部int型數據列表。每個進程先排序各自的局部列表,然后進程0收集並打印這些局部列表。然后,這些進程使用樹形結構通信合並全局列表給進程0,並打印最終結果。
如圖,一般情況下,應由進程讀入數據,然后一層一層的傳到每個進程,每個進程排序完成之后,由上一層的進程進行合並。我這次做的只是輸入數n,由每個進程產生n/comm_sz個隨機數。
這里有一個問題,如何確定一個進程的父節點和子節點?
你最初可能認為讓在處理樹中的每個節點是一個單獨的進程中。這樣,你可以簡單地從二叉堆借用一個想法任何父節點的左子節點的下標2* K+1,右子節點的下標是2* K+2,一個節點的父節點是(K-1 )/ 2,這在一個完全二叉樹中確定了父子關系。因此,一個內部節點將數據分成兩半,並發送給每個子進程進行處理。這樣葉節點,只是做了排序,內部節點等待然后從兩個子節點接收回數據,執行兩半的合並,以及(對於所有內部節點但非根節點本身)將結果發送到父節點。

1 #include <stdio.h> 2 3 void communicate ( int myHeight, int myRank ) 4 { int parent = myRank & ~(1<<myHeight); 5 6 if ( myHeight > 0 ) 7 { int nxt = myHeight - 1; 8 int rtChild = myRank | ( 1 << nxt ); 9 10 printf ("%d sending data to %d\n", myRank, rtChild); 11 communicate ( nxt, myRank ); 12 communicate ( nxt, rtChild ); 13 printf ("%d getting data from %d\n", myRank, rtChild); 14 } 15 if ( parent != myRank ) 16 printf ("%d transmitting to %d\n", myRank, parent); 17 } 18 19 int main ( void ) 20 { int myHeight = 3, myRank = 0; 21 22 printf ("Building a height %d tree\n", myHeight); 23 communicate(myHeight, myRank); 24 return 0; 25 }
下面是輸出結果
Building a height 3 tree
0 sending data to 4
0 sending data to 2
0 sending data to 1
1 transmitting to 0
0 getting data from 1
2 sending data to 3
3 transmitting to 2
2 getting data from 3
2 transmitting to 0
0 getting data from 2
4 sending data to 6
4 sending data to 5
5 transmitting to 4
4 getting data from 5
6 sending data to 7
7 transmitting to 6
6 getting data from 7
6 transmitting to 4
4 getting data from 6
4 transmitting to 0
0 getting data from 4
了解了通信結構之后,下面是mpi_merge_sort.c文件,利用MPI寫的歸並排序,
1 #include<stdlib.h> 2 #include<stdio.h> 3 #include<time.h> 4 #include<math.h> 5 #include<string.h> 6 #include<mpi.h> 7 //讀取輸入的要排序的數的個數,並計算每個進程需要排序的個數 8 void read_n(int* n,int my_rank,MPI_Comm comm,int comm_sz,int* size); 9 //根據節點在通信樹中的最高度為數組分配空間 10 void allocate_arrays(int** left,int** right,int** merge,int size,int height,int* myHeight,int my_rank); 11 //在left數組中生成size個隨機數 12 void randnum(int* left,int size,int my_rank); 13 //以下三個函數時用於在通信樹的葉節點對生成在left中的size個隨機數進行快速排序 14 void swap(int *a,int *b); 15 int partition(int left[],int lo,int hi); 16 void QuickSort(int left[], int lo, int hi); 17 //父節點將接收子節點的數據與自己節點的數據進行歸並 18 void merge_sort(int* left,int* right,int* merge,int size); 19 //控制通信,確定從哪個子節點中接收數據或向哪個父節點發送數據 20 void communicate ( int Height, int my_rank,int size,int* right,int* left,int* merge,MPI_Comm comm); 21 22 int main(int argc,char* argv[]){ 23 int n,comm_sz,my_rank; 24 int* left; 25 int* right; 26 int* merge; 27 int size; 28 int myHeight=0; 29 MPI_Comm comm; 30 int height=0; 31 32 MPI_Init(NULL,NULL); 33 comm=MPI_COMM_WORLD; 34 MPI_Comm_size(comm,&comm_sz); 35 MPI_Comm_rank(comm,&my_rank); 36 read_n(&n,my_rank,comm,comm_sz,&size); 37 int tt=comm_sz; 38 for(int i=0;i<comm_sz;i++){ 39 tt=tt/2; 40 if(tt==0) 41 break; 42 height++; 43 } 44 allocate_arrays(&left,&right,&merge,size,height,&myHeight,my_rank); 45 randnum(left,size,my_rank); 46 QuickSort(left,0,size-1); 47 48 communicate(height,my_rank,size,right,left,merge,comm); 49 if(my_rank==0){ 50 if(height%2!=0) 51 for(int i=0;i<n;i++) 52 printf("%d ",merge[i]); 53 else 54 for(int i=0;i<n;i++) 55 printf("%d ",left[i]); 56 printf("\n"); 57 } 58 MPI_Finalize(); 59 free(left); 60 free(right); 61 free(merge); 62 63 } 64 void read_n(int* n,int my_rank,MPI_Comm comm,int comm_sz,int* size){ 65 if(my_rank==0){ 66 printf("please intput the number of number\n"); 67 scanf("%d",n); 68 } 69 MPI_Bcast (n,1,MPI_INT,0,comm); 70 *size=*n/comm_sz; 71 } 72 void allocate_arrays(int** left,int** right,int** merge,int size,int height,int* myHeight,int my_rank){ 73 for(int i=0;i<height;i++){ 74 int parent=my_rank&~(1<<i); 75 if(parent!=my_rank) 76 break; 77 (*myHeight)++; 78 } 79 *left=malloc((1<<(*myHeight))*size*sizeof(int)); 80 81 *right=malloc((1<<(*myHeight-1))*size*sizeof(int)); 82 *merge=malloc((1<<(*myHeight))*size*sizeof(int)); 83 84 } 85 void randnum(int* left,int size,int my_rank){ 86 srand(my_rank); 87 for(int i=0;i<size;i++) 88 left[i]=rand(); 89 } 90 void swap(int *a,int *b) 91 { 92 int temp=*a; 93 *a=*b; 94 *b=temp; 95 } 96 int partition(int* left,int lo,int hi) 97 { 98 int key=left[hi]; 99 int i=lo-1; 100 for(int j=lo;j<hi;j++) 101 { 102 if(left[j]<=key) 103 { 104 i=i+1; 105 swap(&left[i],&left[j]); 106 } 107 } 108 swap(&left[i+1],&left[hi]); 109 return i+1; 110 } 111 void QuickSort(int *left, int lo, int hi) 112 { 113 if (lo<hi) 114 { 115 int k = partition(left, lo, hi); 116 QuickSort(left, lo, k-1); 117 QuickSort(left, k+1, hi); 118 } 119 } 120 void merge_sort(int* left,int* right,int* merge,int size){ 121 122 int lp=0,rp=0,mp=0; 123 for(int i=0;i<2*size;i++){ 124 if(left[lp]<=right[rp]){ 125 merge[mp]=left[lp]; 126 lp++; 127 mp++; 128 }else{ 129 merge[mp]=right[rp]; 130 rp++; 131 mp++; 132 } 133 if(lp==size||rp==size) 134 break; 135 } 136 if(lp==size){ 137 memcpy(&merge[mp],&right[rp],(size-rp)*sizeof(int)); 138 }else{ 139 memcpy(&merge[mp],&left[lp],(size-lp)*sizeof(int)); 140 } 141 } 142 void communicate ( int Height, int my_rank,int size,int* right,int* left,int* merge,MPI_Comm comm) 143 { 144 for(int i=0;i<=Height;i++){ 145 int parent=my_rank&~(1<<i); 146 if(parent==my_rank){ 147 int rtChild=my_rank|(1<<i); 148 149 MPI_Recv(right,size,MPI_INT,rtChild,0,comm,MPI_STATUS_IGNORE); 150 151 merge_sort(left,right,merge,size); 152 153 int* temp; 154 temp=left; 155 left=merge; 156 merge=temp; 157 size*=2; 158 159 }else{ 160 MPI_Send(left,size,MPI_INT,parent,0,comm); 161 break; 162 163 } 164 } 165 166 }
使用 mpicc -g -Wall -std=c99 -o mpi_merge_sort mpi_merge_sort.c 進行編譯
使用 mpirun -n 4 ./mpi_merge_sort
注意輸入的進程個數一定是2的整數次冪,且輸入的要排序數的個數要能整除進程個數
下面是一次的運行結果:
please intput the number of number
12
190686788 483147985 844158168 846930886 846930886 1205554746 1505335290 1681692777 1681692777 1738766719 1804289383 1804289383
參考資料:http://penguin.ewu.edu/~trolfe/ParallelMerge/ParallelMerge.html