並行歸並排序——MPI


      並行歸並排序在程序開始時,會將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,這在一個完全二叉樹中確定了父子關系因此,一個內部節點將數據分成兩半,並發送給每個子進程進行處理。這樣葉節點,只是做了排序,內部節點等待然后從兩個子節點接收回數據,執行兩半的合並,以及(對於所有內部節點但非根節點本身)將結果發送到父節點。

  但是這樣帶來一個問題,就是當葉節點進行排序時,父節點在等待子節點,沒有理由讓父節點空閑等待子節點進行排序,我們希望讓父節點充當左子節點進行工作,如圖:
http://penguin.ewu.edu/~trolfe/ParallelMerge/PMerge_B.gif
  這樣每個節點將數據分成兩半,每個節點自己處理數據的左半,右子節點處理數據的右半。這樣之后你就要確定每個節點的父節點和子節點,你可以根據樹形通信結構來確定每個節點的父節點和子節點。
  我們要先確定每個節點在樹形通信結構的高度,葉節點的高度為0,這樣根節點0的高度是3。節點0需要與節點4進行通信,對於如何計算出節點0的右子節點是4,可以通過將1轉化成二進制並左移節點0所在的高度3減1位,即(myRank|(1<<2)),myRank表示節點的編號,這里是0,這樣就可以計算出高度為3的節點0的右子節點。下一步節點0需要與節點2通信,節點4需要與節點6通信,此時節點0和4的高度為2,因此需要計算他們的子節點時通過公式((myRank|(1<<1)),這樣計算出節點0的右子節點是2,節點4的右子節點時6,一般化的公式是(myRank|(myHeight-1))。
  計算完每個節點的子節點后,還要計算每個節點的父節點,每個節點的父節點可以通過如下公式進行計算,myRank&~(1<<myHeight)。
  下面是一個小的demo來展示通信過程,他展示了高度為3的通信樹節點之間的通信過程,
 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

 


免責聲明!

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



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