きっかけ
前回、ネットワークブートでラズパイを起動してみた。ネットワークブートで複数のラズパイを同じ環境で起動できたので、MPI(並列処理)をやってみようと思う。
環境構築
-
Raspberry Pi4でネットワークブートするに記載した手順でネットワークブート環境を構築し、以下のようなクラスタを構築する。
注意1:全てのノードに同じユーザアカウントを作成すること(異なるユーザアカウントだと実行できない)
注意2:ブートサーバー側、クライアント側は相互にパスワード無しの鍵認証のsshでアクセスできる状態にすること(送受信メッセージのやり取り等にsshを使っている模様)
注意3:各クライアント側は起動元の領域を共通にしているので、各クライアント個別で異なるファイルを保存することができない。また、swapも無効にしたほうが良いと思われる。(あるノードが他ノードのメモリマネジメントなんて把握しているとは思えないので。。。)
- 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/
- 設定ファイルの作成
新規ファイルを作成し、各ノードのIPアドレスとrank数(コア数)を記述する
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
と同じディレクトリに作成する
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を送信)
}
- コンパイルする
$ mpic++ -o prime-number_mpi prime-number_mpi.c
- コンパイルした実行形式
prime-number_mpi
をクライアント側のホームディレクトリにコピーする(各ノードに同じ実行形式が無いと実行できない)
並列処理の実行
- 以下コマンドで並列処理を実行する
$ mpirun -hostfile hosts -np 16 ./prime-number_mpi
#-hostfile でノードの情報を指定する
#-np で 総ランク数を指定する
- 実行結果は以下の通り
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
.
.
.
ラズパイ1個のみで同様の処理を実行した場合と比べると、上記並列実行によりやや速くなったが、
並列実行させているrank数分(15倍)の速さにはなっていない模様。通信のレイテンシ等、遅くなるのかもしれない。
参考にしたサイト