Xubuntu16.04でOpenMPIを使って並列処理をしてみる

#OpenMPIの導入
aptを使ってOpenMPIのライブラリと実行環境？を導入する。ぐぐるとすぐ出てくるけど、パッケージ名が変わった(openmpi-devがlibopenmpi-devになった？)みたいなんで、次のコマンドで導入する。

```shell
$sudo apt-get install openmpi-doc openmpi-bin libopenmpi-dev
```

#とりあえず並列処理でHello, World
とりあえず並列処理でHello, Worldをしてみる。プロセス番号とHello, Worldを表示する。言語はC言語で書きます。Fortranの人もそんなに変わらないかと…

```c:Hello.c
#include <mpi.h>
#include <stdio.h>

int main( int args, char **argv )
{
   int rank, size;
   MPI_Init( &args, &argv ); // 並列処理の開始
   MPI_Comm_rank( MPI_COMM_WORLD, &rank ); // ランク(プロセス番号のようなもの)の取得
   MPI_Comm_size( MPI_COMM_WORLD, &size ); // 全ランク数の取得
   fprintf( stdout, "Hello, World from proc: [%d].\n", rank );
   MPI_Finalize(); // 並列処理の終わり
   return 0;
}
```

これをコンパイルするにはmpiccを使う。コンパイルオプションとかはgccと同じ感じなのかな？
とりあえず`-Wall`と`-o`は使えました。

```shell-session
$mpicc Hello.c -o hello -Wall
```

コンパイルできたら実行する。実行するには`mpirun`を使う。

```shell-session
$mpirun -np 4 ./hello
Hello, world from proc: 1.
Hello, world from proc: 0.
Hello, world from proc: 2.
Hello, world from proc: 3.
```

`-np`は並列処理するプロセスの数を指定するもの。今回は4を指定したので4つのプロセスから、それぞれ`fprintf`を実行するので4つ表示される。綺麗に0から3まで並ばないのは、並列処理してるからで、それぞれにstdoutに書き込むから。Linuxの排他制御はよくしらないけど、たぶんMutex的なものな気がするから、先に取得した順に表示してる気がする。表示される順番は実行するたびに変わります。
ふつうに`./hello`で実行すると、並列処理がされないよ。

#プロセス間通信の練習でn!を求めるプログラムを書いてみる
OpenMPIでは並列化するときにそれぞれのプロセスにランクと呼ばれる番号がつけられる。このランクを使ってプロセス間の通信ができる。今回はランク0のプロセスをまとめ役プロセスにして、ランク1~nのプロセスはランク0に自身のランクを送信して、ランク0のプロセスは送られてきたデータを加算していくことでn!を求めたいと思います。
というわけで、プログラムです。

```c:factorial.c
#include <mpi.h>
#include <stdio.h>

int main( int argc, char **argv )
{
    int my_rank;
    int size;
    int local_result;
    int result = 0;
    MPI_Status status;

    int count;

    const int TAG = 0;
    const int LOAD = 0;
    const int DESTINATION = 0;

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

    if( my_rank == 0 ) // まとめ役プロセスでの処理。 1~nプロセスからのデータを受け取って加算。
        for( count = 0; count < size; count++ ) {
            MPI_Recv( &local_result, LOAD, MPI_INT, cnt, TAG, MPI_COMM_WORLD, &status );
            fprintf( stdout, "Proc[%d]\n", local_result );
            result += local_result;
        }
        fprintf( stdout, "Total: %d.\n", result ;
    }
    else { // 1~nプロセスでの処理。　ランク0プロセスに向かって自身のランクを送信。
        MPI_Send( &my_rank, LOAD, MPI_INT, DESTINATION, TAG, MPI_COMM_WORLD );
    }

    MPI_Finalise();

    return 0;
}
```

実行するときのプロセス数－1がnなります。こんな感じに

```shell-session
$mpirun -np 11 factrial
```

ってすると、

```shell-session
Total: 55.
```

と、表示されます。
Linuxで`fork`とかしたことある人はなにやってるかわかると思いますが、簡単に動作を説明しておきます。
`MPI_Init`を実行すると、その時点から実行時に指定した数のプロセスが生成されます。ここで注意が必要なのは、`MPI_Init`以降のプログラムは生成されたプロセスそれぞれで実行されることです。
したがって、`MPI_Comm_rank`を実行することで、自プロセスのランクを取得して、そのランクを`if`で比較することでプロセスごとに処理を分岐します。

- ランク0であれば、他のプロセスからのデータを待つ。
- ランク0以外であれば、ランク0プロセスにデータを送信。

今回プロセス間通信には`MPI_Send`と`MPI_Recv`を使用しました。これはブロッキング型の通信関数です。ブロッキングとノンブロッキングはその手のプロの方がいらっしゃると思うのでここでは深い説明は避けますが、ブロッキングでは通信の処理が終了するまで全体の処理がそこで停止します。ノンブロッキングでは通信処理が完了しなくても次の処理に進んでしまいます。

`MPI_Send`と`MPI_Recv`の引数は、

- `MPI_Send(void *buf, int count, MPI_Datatype type, int dest, int tag, MPI_Comm comm)`
	- データ, データの個数, データ型, 宛先のランク番号, 荷物のタグ, コミュニケータ(まぁMPI_COMM_WORLDでいいんじゃないでしょうか…)
- `MPI_Recv(void *buf, int count, MPI_Datatype type, int source, int tag, MPI_Comm comm, MPI_Status *status)`
	- データ, データの個数, データ型, 送信元のランク番号, 荷物のタグ, コミュニケータ(まぁMPI_COMM_WORLDでいいんじゃないでしょうか…), ステータス(Fortranのみ)

となっています。