Raspberry PI 4 で MPI(並列処理)をやってみた

## きっかけ
　前回、ネットワークブートでラズパイを起動してみた。ネットワークブートで複数のラズパイを同じ環境で起動できたので、MPI(並列処理)をやってみようと思う。
 
## 環境構築
 - [Raspberry Pi4でネットワークブートする](https://qiita.com/_shin_/items/219ad32ef96acb48e8b7)に記載した手順でネットワークブート環境を構築し、以下のようなクラスタを構築する。
 注意1：全てのノードに同じユーザアカウントを作成すること(異なるユーザアカウントだと実行できない)
 注意2：ブートサーバー側、クライアント側は相互にパスワード無しの鍵認証のsshでアクセスできる状態にすること(送受信メッセージのやり取り等にsshを使っている模様)
 注意3：各クライアント側は起動元の領域を共通にしているので、各クライアント個別で異なるファイルを保存することができない。また、swapも無効にしたほうが良いと思われる。(あるノードが他ノードのメモリマネジメントなんて把握しているとは思えないので。。。)
 ![node.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513034/5184a86c-41ab-3351-cc78-06bbe38c010c.png)
<br>

- OpenMPIのインストール
  - ブートサーバー側、クライアント側それぞれにMPIで必要なソフトをインストールする
  ```
    $ sudo apt install openmpi-bin openmpi-common openmpi-doc libopenmpi-dev libgtk2.0-dev librdmacm-dev -y
  ```
  - ブートサーバー側、クライアント側でOpenMPIのバージョンが一致していることを確認する
    注意：(おそらく)メジャーバージョンが一致していないと実行できない
  ```
    $ mpirun --version
    mpirun (Open MPI) 4.1.4
    
    Report bugs to http://www.open-mpi.org/community/help/
  ```
<br>

- 設定ファイルの作成
　新規ファイルを作成し、各ノードのIPアドレスとrank数（コア数)を記述する
 ```bash:hosts
 192.168.0.aaa cpu=4
 192.168.0.bbb cpu=4
 192.168.0.ccc cpu=4
 192.168.0.ddd cpu=4
 ```


## プログラム作成

- 試しにプログラムを作成
  500000000以上の素数を計算し、表示するプログラムを前の手順で作成した```hosts```と同じディレクトリに作成する 

```c++:prime-number_mpi.cpp
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<unistd.h>
#include <stdio.h>
#include <mpi.h>
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdint>

void searchPrimeNum(int rank, long& i,long& number);

int main()
{
  long i;
 
  long number = 500000000; //計算を始める数
  long thre_number;
  
  int nproc,rank;
  MPI_Init(NULL, NULL); //MPIを初期化
  MPI_Comm_size(MPI_COMM_WORLD, &nproc); //クラスタの総ランク数(コアの数)を取得
  MPI_Comm_rank(MPI_COMM_WORLD, &rank); //各ランクの番号を取得
  printf("%d in %d\n", rank, nproc); //各ランクの番号と総ランク数を表示
  MPI_Barrier(MPI_COMM_WORLD); //各ランクの処理の同期をここでとる(全てのランクがここまで処理が進むと、これ以降の処理に進むことができる)
 
while (number > 0) {

	thre_number = number + (long)rank ;
	if(rank != 0){
		searchPrimeNum(rank, i, thre_number); //rank0以外に素数の計算をさせる
	}
	MPI_Barrier(MPI_COMM_WORLD); //各ランクの処理の同期をとる
	if(rank == 0){
		for(int j = 1; j < nproc; ++j ){

			MPI_Recv(&thre_number, 1, MPI_LONG, j, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); //rank0 はそれ以外のrankから素数の計算結果を受信する
			if( thre_number != -1){
				std::cout << thre_number  << std::endl; //素数であるならば表示する(素数でないなら-1を代入しているので表示しない)
			}

		}
	}

	number = number + nproc -1;

}
MPI_Finalize();
return 0;

}

void searchPrimeNum(int rank, long& i, long& number){
	int flag = 0;
	for( i=2;i<number;++i ) {
		if( number%i==0 ) { 
			flag = 1;
			number = -1; //素数でない場合は-1を代入 (2以上で自分自身以外で割れる値がある場合)
			break;
		}
	}
	MPI_Send(&number , 1, MPI_LONG, 0, 0, MPI_COMM_WORLD); //rank0に値を送信する(素数であるならその値を、素数でないなら-1を送信)
}
```
<br>

- コンパイルする

```bash
$ mpic++ -o prime-number_mpi prime-number_mpi.c
```
<br>

- コンパイルした実行形式```prime-number_mpi```をクライアント側のホームディレクトリにコピーする(各ノードに同じ実行形式が無いと実行できない)

## 並列処理の実行

- 以下コマンドで並列処理を実行する
```bash
$ mpirun -hostfile hosts -np 16 ./prime-number_mpi
#-hostfile でノードの情報を指定する
#-np で 総ランク数を指定する
```
<br>

- 実行結果は以下の通り
```
0 in 16
1 in 16
2 in 16
3 in 16
4 in 16
12 in 16
5 in 16
13 in 16
6 in 16
14 in 16
7 in 16
15 in 16
11 in 16
8 in 16
9 in 16
10 in 16
500000003
500000009
500000041
500000057
500000069
500000071
500000077
500000089
500000093
500000099
500000101
.
.
.
```

<br>

 ラズパイ1個のみで同様の処理を実行した場合と比べると、上記並列実行によりやや速くなったが、
 並列実行させているrank数分(15倍)の速さにはなっていない模様。通信のレイテンシ等、遅くなるのかもしれない。


## 参考にしたサイト

https://curc.readthedocs.io/en/latest/programming/MPI-C.htmlf