C言語+MPIのtips（MPI_Gatherv）

MPI_Gatherv は「各プロセスの持つ必ずしも同じ長さでないデータを結合して，あるプロセスに渡す関数」。

※MPI_Gatherv は「各プロセスの持つ同じ長さのデータを結合して，あるプロセスに渡す関数」だったことに注意。

【目的】
各プロセスに存在する一次元配列 send から適当な数（eachsz）の要素だけ取り出して、プロセス0にある一次元配列 recv に結合させることでMPI_Gatherv の機能を知る。


【使用するもの】 
open-mpi

【環境】
 $ mpicc —version 
Apple clang version 11.0.3 (clang-1103.0.32.29)  
Target: x86_64-apple-darwin19.6.0  
Thread model: posix 
 InstalledDir: /Library/Developer/CommandLineTools/usr/bin 

【方法】

```Gatherv.c
#include<stdio.h>
#include<mpi.h>

int main(int argc, char **argv){
        int i;
        int my_rank, num_proc;
        int send[10];
        int recv[20];                // The number of processes is 2
        int eachsz[2] = {3,8};       // The number of processes is 2
        int place[2];                // The number of processes is 2

        MPI_Init(&argc, &argv);
        MPI_Comm_size(MPI_COMM_WORLD, &num_proc);
        MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);

        for(i=0;i<10;i++) send[i] = my_rank*10 + i;

        for(i=0;i<10;i++) printf("my_rank:%d, send[%d] = %d\n",my_rank,i,send[i]);

        for(i=0;i<20;i++) recv[i] = -1;
        for(i=0;i<2;i++) place[i] = 10*i;

        MPI_Gatherv(send, eachsz[my_rank], MPI_INT, recv, eachsz, place, MPI_INT, 0,MPI_COMM_WORLD);

        printf("\n");
        printf("my_rank:%d\n",my_rank);
        for(i=0;i<20;i++) printf("%2d ",recv[i]);
        printf("\n");

MPI_Finalize();
return 0;
```

【結果】
mpicc コマンドでコンパイル、mpirun コマンドでプロセス数を２にして実行。

```
$ mpicc Gatherv.c
$ mpirun -np 2 ./a.out
my_rank:0, send[0] = 0
my_rank:0, send[1] = 1
my_rank:0, send[2] = 2
my_rank:0, send[3] = 3
my_rank:0, send[4] = 4
my_rank:0, send[5] = 5
my_rank:0, send[6] = 6
my_rank:0, send[7] = 7
my_rank:0, send[8] = 8
my_rank:0, send[9] = 9
my_rank:1, send[0] = 10
my_rank:1, send[1] = 11
my_rank:1, send[2] = 12
my_rank:1, send[3] = 13
my_rank:1, send[4] = 14
my_rank:1, send[5] = 15
my_rank:1, send[6] = 16
my_rank:1, send[7] = 17
my_rank:1, send[8] = 18
my_rank:1, send[9] = 19

my_rank:1
-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 

my_rank:0
 0  1  2 -1 -1 -1 -1 -1 -1 -1 10 11 12 13 14 15 16 17 -1 -1 
```

【考察（何が起きたのか）】
まずプロセス０と１でそれぞれ send[0]、send[1]、…、send[9] が生成される。
プロセス０と１でそれぞれ recv[0]、recv[1]、…、recv[19] が生成される。
プロセス０と１でそれぞれ eachsz[0]（=3）、eachsz[1]（=8） が生成される。
プロセス０と１でそれぞれ place[0]、place[1] が生成される。

それぞれのプロセスでのfor文で値が格納される。 
 at プロセス０　send[0]=0*10+0=0、send[1]=0*10+1=1、…、send[9] =0*10+9=9
 at プロセス１　send[0]=1*10+0=10、send[1]=1*10+1=11、…、send[9] =1*10+9=19

at プロセス０ recv[0]=-1、 recv[1]=-1、…、recv[19]=-1
at プロセス１ recv[0]=-1、 recv[1]=-1、…、recv[19]=-1

at プロセス０ place[0]=0、 place[1]=10
at プロセス１ place[0]=0、 place[1]=10

MPI_Gathervによって各プロセスの「send」から「eachsz[my_rank]」個の要素を送る。つまり、プロセス０からは３個（eachsz[0]=3）、プロセス１からは８個（eachsz[1]=8）の要素が送られる。送られるデータの型は整数（int）「MPI_INT」。一つのプロセスあたりの送信するデータの数は「eachsz」、各プロセスから受け取るデータの場所（配列のインデックス）は「place」。つまり、プロセス０からの３個のデータは place[0] = 0 よりrecv[0]、recv[1]、recv[2]に代入される。よって
recv[0]（= プロセス０の send[0]）=0、
recv[1]（= プロセス０の send[1]）=1、
recv[2]（= プロセス０の send[2]）=2
となる。さらにプロセス１からの８個のデータは place[1] = 10 よりrecv[10]、recv[11]、…、recv[17]に代入される。よって
recv[10]（= プロセス１の send[0]）=10、
recv[11]（= プロセス１の send[1]）=11、
…、
recv[17]（= プロセス１の send[7]）=17
となる。recv[3]からrecv[9]、また recv[18]からrecv[19]は -1 のまま。
受け取るデータの型は整数（int）「MPI_INT」。結果はプロセス「0」へ送信される。プロセス１の recv の要素は-1のまま。

それぞれのプロセスで recv が表示されて終了。

【参考】 
「mpi 並列プログラミング」と検索すると、より正確でより情報量の多い資料が見つかります。

【補足】
実際は recv と place はプロセス０にさえあれば良いので、以下参考にどうぞ。

```Gatherv_2.c
#include<stdio.h>
#include<mpi.h>
#include<stdlib.h>          // For malloc
#include<time.h>            // For generating random numbers

#define N 10

int main(int argc, char **argv){
        int i;
        int my_rank, num_proc;
        int send[N];
        int *recv;
        int *eachsz;
        int *place;
        srand(time(NULL));          // For generating random numbers

        MPI_Init(&argc, &argv);
        MPI_Comm_size(MPI_COMM_WORLD, &num_proc);
        MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);

        for(i=0;i<N;i++) send[i] = my_rank*N + i;
        for(i=0;i<N;i++) printf("my_rank:%d, send[%d] = %d\n",my_rank,i,send[i]);

        if (my_rank==0){
           recv=(int *)malloc(num_proc*N*sizeof(int));
           if (recv==NULL){
                printf("recv\n");
                return 1;
           }
           for(i=0;i<N*num_proc;i++) recv[i]=-1;

           place=(int *)malloc(num_proc*sizeof(int));
           if (place==NULL){
                printf("place\n");
                return 1;
           }
           for(i=0;i<num_proc;i++) place[i] = N*i;

        }

        eachsz=(int *)malloc(num_proc*sizeof(int));
        if (eachsz==NULL){
                printf("eachsz\n");
                return 1;
        }
        for(i=0;i<num_proc;i++)  eachsz[i]=rand()%N;

        if (my_rank==0){
           for(i=0;i<num_proc;i++)  printf("eachsz[%d] = %d\n",i, eachsz[i]);
        }

        MPI_Gatherv(send, eachsz[my_rank], MPI_INT, recv, eachsz, place, MPI_INT, 0,MPI_COMM_WORLD);

        if (my_rank==0){
           printf("\n");
           printf("my_rank:%d\n",my_rank);
           for(i=0;i<20;i++) printf("%2d ",recv[i]);
           printf("\n");
        }

        if (my_rank==0){
           free(recv); free(place);
        }

       free(eachsz);

MPI_Finalize();
return 0;
}
```