MPIの使い方

MPIの使い方や、動作の仕方といったことのメモ

MPIとは?

MPIは「Message Passing Interface」ってもので、要するにメッセージでの送受信を行いノード間の協調を図るもの。
これ自体がプログラミング言語なのではなく、ライブラリである。
使用する際には、「mpi.h」または「mpi++.h」をインクルードする必要がある。

メッセージの送受信には用意されている関数を用いる。これにも色々な方法があるよう。


MPIの動作

基本的にMPIを使用する用にコンパイルしたものは、指定したノードの数で実行される。(ノードの数の指定はシェルスクリプトで)
各ノードで実行されるプログラムは実際には同じものが動いている。つまり、すべてmain関数から処理を行っている。

では、何が利点か?それは、各ノードにはプロセス毎にノードの番号(rank)が割り当てられており、これによってif文などを用いて処理の分担を行う。

つまり、MPIは「並列処理」というよりも「分散処理」という側面が強いとも言える。


MPIの使い方

とりあえずサンプルを載せる。

#include<stdio.h>
#include<string.h>
#include<iostream>
#include "mpi++.h"

using namespace std;

int main(int argc, char* argv[]){
      int my_rank;              //ノードの番号
       int p;                    //すべてのノードの合計数
       int source;               //メッセージの送信元番号
       int dest;                 //メッセージの送信先番号
       int tag = 0;              //メッセージタグ
       char message[100];        //メッセージ
       MPI_Status status;        

       MPI_Init(&argc, &argv);   //MPI関数を呼び出すための初期化

       MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);   //今プログラムが走っているノードの番号(rank)を取得

       MPI_Comm_size(MPI_COMM_WORLD, &p);         //ノードの合計数を取得

       //printf("Hello! %d\n", my_rank);

       //cout << "Hello! " << my_rank << endl;
       
       //rankが0でないノードでのみ実行する
       if(my_rank != 0){
               //メッセージに以下の文を格納する
               sprintf(message, "Greetings from process %d", my_rank);

               //printf("My rank is %d\n", my_rank);

               //cout << "My rank is " << my_rank << endl;

               dest = 0;   //0ノードに送信先を設定
 
               //0ノードにmessageを送信
               MPI_Send(message, strlen(message)+1, MPI_CHAR, dest, tag, MPI_COMM_WORLD);
       }
       //0ノードで実行される
       else{
               //全ノード分ループをまわす
               for(source = 1; source < p; source++){
                       //source番目のノードからmessageを受け取り、
                       MPI_Recv(message, 100, MPI_CHAR, source, tag, MPI_COMM_WORLD, &status);
                       //表示
                       printf("%s\n", message);
                       //cout << message << endl;
               }
       }
       //最後の処理 
       MPI_Finalize();

}

出力結果

%NQSII(INFO): ------- Output from job:0000 -------
Warning: no access to tty (Bad file descriptor).
Thus no job control in this shell.
[0] Greetings from process 1
[0] Greetings from process 2
[0] Greetings from process 3
[0] Greetings from process 4
[0] Greetings from process 5
[0] Greetings from process 6
[0] Greetings from process 7
[0] Greetings from process 8
[0] Greetings from process 9

%NQSII(INFO): ------- Output from job:0001 -------

%NQSII(INFO): ------- Output from job:0002 -------

%NQSII(INFO): ------- Output from job:0003 -------

%NQSII(INFO): ------- Output from job:0004 -------

  • 出力の左端[0]は、0ノードでの出力を表している

  • 各ノードでmain関数がそのまま流れており、if文でmy_rankを判定材料として処理を分担している。
  • 各ノードは0ノードにメッセージを送り、0ノードはそれらを逐一読み取り、表示している。
  • どのメッセージをどこで受信するかを示すもの
 →つまり、このタグが合うところでのみメッセージの受信ができる

  • 上記のサンプルで、コメントアウトしているprintfとcoutがある。
 →printfはしっかり出力をするが、coutは他のスレッドの割り込みを禁止することができていないようで、表示が割り込まれることがあるので使用しないことをオススメする

printfをコメントアウトしなかった場合の出力は以下

%NQSII(INFO): ------- Output from job:0000 -------
Warning: no access to tty (Bad file descriptor).
Thus no job control in this shell.
[0] Hello! 0
[1] Hello! 1
[1] My rank is 1
[0] Greetings from process 1
[0] Greetings from process 2
[0] Greetings from process 3
[0] Greetings from process 4
[0] Greetings from process 5
[0] Greetings from process 6
[0] Greetings from process 7
[0] Greetings from process 8
[0] Greetings from process 9

%NQSII(INFO): ------- Output from job:0001 -------
[2] Hello! 2
[3] Hello! 3
[3] My rank is 3
[2] My rank is 2

%NQSII(INFO): ------- Output from job:0002 -------
[4] Hello! 4
[4] My rank is 4
[5] Hello! 5
[5] My rank is 5 

%NQSII(INFO): ------- Output from job:0003 -------
[6] Hello! 6
[6] My rank is 6
[7] Hello! 7
[7] My rank is 7

%NQSII(INFO): ------- Output from job:0004 -------
[8] Hello! 8
[8] My rank is 8
[9] Hello! 9
[9] My rank is 9

  • 各ノードでの出力がどれになっているか注意

  • また、スパコンでのMPIのプログラムの出力は、outputのファイルはできない。
  • 出力結果は、実行後に作成される「run.sh.o(ジョブID)」というファイルに書き込まれている。
  • また、同時に出力されている「run.sh.e(ジョブID)」はエラー関係が出力されている。

さらにシェルスクリプトを紹介

#PBS -l cputim_job=00:05:00     //cpu動作時間
#PBS -l memsz_job=2gb           //メモリサイズ
#PBS -l cpunum_job=2            //使用コア数
#PBS -T vltmpi                 
#PBS -b 5                       //ノード数
#PBS -q PCL-B                   //使用マシン

cd MPI_sample                                   //ディレクトリ移動
mpirun_rsh -np 10 ${NQSII_MPIOPTS} ./a.out      //数字の10は(ノード数)×(CPUコア数)


内積

これには、MPI_Reduce()という関数を使う。とりあえずサンプル。

#include<stdio.h>
#include<string.h>
#include"mpi++.h"

using namespace std;

//内積を実際に計算する関数(局所的な計算)
int Serial_dot(int x[], int y[], int n){

       int i;
       int sum = 0;

       for(i = 0; i < n; i++)
               sum = sum + x[i]*y[i];

       return sum;
}
//各ノードの内積をまとめる関数
//各ノードでSerial_dot()でそれぞれの内積を求めて、MPI_Reduce()でまとめる
//0ノード以外の返り値は0
int Parallel_dot(int local_x[], int local_y[], int n_bar){

       int local_dot;
       int dot = 0.0;
       int Serial_dot(int x[], int y[], int m);

       local_dot = Serial_dot(local_x, local_y, n_bar);
       printf("local_dot = %d\n", local_dot);
       MPI_Reduce(&local_dot, &dot, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);

       return dot;

}
int main(int argc, char* argv[]){

       int n = 20;     //全配列数
       int my_rank;    //ノード番号
       int p;          //全ノード数

       //MPIの初期化
       MPI_Init(&argc, &argv);

       //ノード番号取得
       MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);

       //全ノード数取得
       MPI_Comm_size(MPI_COMM_WORLD, &p);

       int n_bar = n/p;   //各ノードでの配列数
       int x[n_bar];
       int y[n_bar];
       int dot;           //求める内積
 
       //printf("n_bar = %d\n", n_bar);

       //初期値設定
       for(int i = 0; i < n_bar; i++){
               x[i] = my_rank;
               y[i] = my_rank;
       }
       
       //各ノードでの内積計算
       dot = Parallel_dot(x,y,n_bar);
       //if(my_rank == 0)
               printf("dot = %d\n", dot);

       MPI_Finalize();

}

出力結果

%NQSII(INFO): ------- Output from job:0000 -------
Warning: no access to tty (Bad file descriptor).
Thus no job control in this shell.
[0] local_dot = 0
[1] local_dot = 2
[1] dot = 0
[0] dot = 570

%NQSII(INFO): ------- Output from job:0001 -------
[2] local_dot = 8
[3] local_dot = 18
[3] dot = 0
[2] dot = 0

%NQSII(INFO): ------- Output from job:0002 -------
[4] local_dot = 32
[5] local_dot = 50
[5] dot = 0
[4] dot = 0

%NQSII(INFO): ------- Output from job:0003 -------
[6] local_dot = 72
[7] local_dot = 98
[7] dot = 0
[6] dot = 0

%NQSII(INFO): ------- Output from job:0004 -------
[8] local_dot = 128
[9] local_dot = 162
[9] dot = 0
[8] dot = 0



MPI_Reduce()

計算を行い、指定した根ノードにのみ計算結果を返す。
そのため、他の各ノードでは返り値は0である。
各引数は次のようになっている。

int MPI_Reduce(
   void          operand,      //計算したい値のポインタ
   void          result,       //計算した結果を保存するポインタ
   int           count,        //データをいくつ送るか(数字1つなら1)
   MPI_Datatype  datatype,     //送信するデータの型(上ではMPI_INT)
   MPI_Op        operator,     //なんの計算をするか(上では総和をとるのでMPI_SUM)
   int           root,         //根ノードの番号(0でよい)
   MPI_Comm      comm          //特になにもなければMPI_COMM_WORLDでよい
){} 

  • MPI_Opについては定義済み操作が多く存在する。それらに関しては、windowsサーバに置いてあるMPIの教科書(p81)を参照のこと
  • 計算結果が格納されるのは、rootに指定したノードでのresultにのみ。

MPI_Allreduce()

MPI_Reduce()では根以外のノードでの返り値が0であったが、これでは全てのノードに計算結果を返すことができる。
引数はほぼMPI_Reduce()と同じ。

int MPI_ALLreduce(
   void          operand,      //計算したい値のポインタ
   void          result,       //計算した結果を保存するポインタ
   int           count,        //データをいくつ送るか(数字1つなら1)
   MPI_Datatype  datatype,     //送信するデータの型(上ではMPI_INT)
   MPI_Op        operator,     //なんの計算をするか(上では総和をとるのでMPI_SUM)
   MPI_Comm      comm          //特になにもなければMPI_COMM_WORLDでよい
){} 

上記のサンプルプログラムで、MPI_Reduce()をMPI_AllReduce()にしたときの出力結果を示す

%NQSII(INFO): ------- Output from job:0000 -------
Warning: no access to tty (Bad file descriptor).
Thus no job control in this shell.
[0] local_dot = 0
[1] local_dot = 2
[0] dot = 570
[1] dot = 570

%NQSII(INFO): ------- Output from job:0001 -------
[2] local_dot = 8
[3] local_dot = 18
[2] dot = 570
[3] dot = 570

%NQSII(INFO): ------- Output from job:0002 -------
[4] local_dot = 32
[5] local_dot = 50
[5] dot = 570
[4] dot = 570

%NQSII(INFO): ------- Output from job:0003 -------
[6] local_dot = 72
[7] local_dot = 98
[7] dot = 570
[6] dot = 570

%NQSII(INFO): ------- Output from job:0004 -------
[8] local_dot = 128
[9] local_dot = 162
[9] dot = 570
[8] dot = 570


行列とベクトルの積

マトリクスとベクトルの積を並列に行うプログラム。

#include<stdio.h>
#include<string.h>
#include<vector>
#include"mpi++.h"

using namespace std;

vector<double> MV(vector<vector<double> >::iterator local_A, 
     int m, int n, vector<double>:: iterator local_x, int local_m, int local_n){

       vector<double> local_y;    //計算用の一時変数
       local_y.resize(local_n);

       vector<double> global_x;  //ベクトル全体
       global_x.resize(n);

       vector<double> global_y;  //計算結果のベクトル
       global_y.resize(n);

       //各プロセスからベクトルxを集める
       MPI_Allgather(local_x, local_n, MPI_DOUBLE, global_x.begin(), local_n, MPI_DOUBLE, MPI_COMM_WORLD);

       //掛け算
       for(int i = 0; i < local_m; i++){
               local_y[i] = 0.0;
               for(int j = 0; j < n; j++)
               local_y[i] = local_y[i] + local_A[i][j]*global_x[j];
       }

       //計算結果を集める
       MPI_Allgather(local_y.begin(), local_n, MPI_DOUBLE, global_y.begin(), local_n, MPI_DOUBLE, MPI_COMM_WORLD);

       return global_y;

}

int main(int argc, char* argv[]){

       int n = 20;
       int m = 20;
       int my_rank;
       int p;

       MPI_Init(&argc, &argv);

       MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);

       MPI_Comm_size(MPI_COMM_WORLD, &p);

       int local_n = n/p;
       int local_m = m/p;
       vector<double> x;
       vector<vector<double> > A;          //掛け算する行列(各プロセスで一部ずつをもつ)
       A.resize(local_m);
       x.resize(local_n);                  //掛け算するベクトル(各プロセスで一部ずつをもつ)
       for(int i = 0; i < local_m; i++)
         A[i].resize(n);

       for(int a = 0; a < local_m; a++){
         x[a] = my_rank;
         for(int b = 0; b < n; b++)
           A[a][b] = 1.0;
       }

       //行列×ベクトル
       vector<double> y;
       y = MV(A.begin(),m,n,x.begin(),local_m,local_n);

       for(int i = 0; i < n; i++)
               printf("y[%d] = %f\n",i,y[i]);

       MPI_Finalize();

}

すぐに使えるであろう関数の形にまで落としこんだ。
行列×ベクトルを行っている部分をこれで置き換えれば、それで事足りるはず。
詳細な仕様はここには書きづらいので、直接聞いてもらえるといいかも。もちろんプログラムから察してくれれば一番だが。
が、簡単に書いておく

・行列A
n×n行列。行方向で区切ったものを各プロセスでもつ。つまりlocal_Aはlocal_m×n行列になっている。

・ベクトルx
n行ベクトル。行方向で区切った部分を各プロセスでもつ。つまり各プロセスではlocal_m行のベクトル。

・プロセス数と行数・列数
当然のことながら、行数・列数はプロセス数で割り切れなければならない。

最後に取得するベクトルyはn行の完全なベクトルになっている。上記のプログラムの出力結果は以下。
プロセス数10・行数列数20で計算

%NQSII(INFO): ------- Output from job:0000 -------
Warning: no access to tty (Bad file descriptor).
Thus no job control in this shell.
[0] y[0] = 90.000000
[0] y[1] = 90.000000
[0] y[2] = 90.000000
[0] y[3] = 90.000000
[0] y[4] = 90.000000
[0] y[5] = 90.000000
[0] y[6] = 90.000000
[0] y[7] = 90.000000
[0] y[8] = 90.000000
[0] y[9] = 90.000000
[0] y[10] = 90.000000
[0] y[11] = 90.000000
[0] y[12] = 90.000000
[0] y[13] = 90.000000
[0] y[14] = 90.000000
[0] y[15] = 90.000000
[0] y[16] = 90.000000
[0] y[17] = 90.000000
[0] y[18] = 90.000000
[0] y[19] = 90.000000
[1] y[0] = 90.000000
[1] y[1] = 90.000000
[1] y[2] = 90.000000
[1] y[3] = 90.000000
[1] y[4] = 90.000000
[1] y[5] = 90.000000
[1] y[6] = 90.000000
[1] y[7] = 90.000000
[1] y[8] = 90.000000
[1] y[9] = 90.000000
[1] y[10] = 90.000000
[1] y[11] = 90.000000
[1] y[12] = 90.000000
[1] y[13] = 90.000000
[1] y[14] = 90.000000
[1] y[15] = 90.000000
[1] y[16] = 90.000000
[1] y[17] = 90.000000
[1] y[18] = 90.000000
[1] y[19] = 90.000000

 
… 
…
‥(以下同様)


行列と行列の積

一応できた。早いかはしらんw

#include<stdio.h>
#include<string.h>
#include<vector>
#include"mpi++.h"

using namespace std;

vector<vector<double> > MV(vector<vector<double> >::iterator local_A, int m, int n, 
   vector<vector<double> >::iterator local_x, int  local_m, int local_n){

	vector<double> local_y;
	local_y.resize(local_n);

	vector<double> global_x;
	global_x.resize(n);

	vector<vector<double> > global_y;
	global_y.resize(n);
	for(int i = 0; i < n; i++)
		global_y.resize(n);	

	vector<double> global_tmp;
	global_tmp.resize(n);

	for(int k = 0; k < n; k++){	
		MPI_Allgather(local_x[k].begin(), local_n, MPI_DOUBLE, 
                                     global_x.begin(), local_n, MPI_DOUBLE, MPI_COMM_WORLD);

		for(int i = 0; i < local_m; i++){
   			local_y[i] = 0.0;
   			for(int j = 0; j < n; j++)
     				local_y[i] = local_y[i] + local_A[i][j]*global_x[j];
  		}
		MPI_Allgather(local_y.begin(), local_n, MPI_DOUBLE, 
                                     global_tmp.begin(), local_n, MPI_DOUBLE, MPI_COMM_WORLD);
		global_y[k] = global_tmp;
	}

	return global_y;

} 

int main(int argc, char* argv[]){ 

	int n = 20;
	int m = 20;
	int my_rank;
	int p;

	MPI_Init(&argc, &argv);

	MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); 

	MPI_Comm_size(MPI_COMM_WORLD, &p);         

	int local_n = n/p;
	int local_m = m/p;
	vector<vector<double> > A;
	vector<vector<double> > x;
	A.resize(local_m);
	for(int i = 0; i < local_m; i++)
		A[i].resize(n);
	x.resize(n);
	for(int i = 0; i < n; i++)
		x[i].resize(local_m);

	for(int a = 0; a < local_m; a++){
		for(int b = 0; b < n; b++){
			A[a][b] = 1.0;
			x[b][a] = my_rank;
		}
	}
	
	vector<vector<double> > y;
	y = MV(A.begin(),m,n,x.begin(),local_m,local_n);
		
	for(int i = 0; i < n; i++){
		for(int j = 0; j < m; j++)
			printf("y[%d][%d] = %f\n",j,i,y[j][i]);
	}

	MPI_Finalize();

}

出力結果

%NQSII(INFO): ------- Output from job:0000 -------
Warning: no access to tty (Bad file descriptor).
Thus no job control in this shell.
[0] y[0][0] = 90.000000
[1] y[0][0] = 90.000000
[1] y[1][0] = 90.000000
[1] y[2][0] = 90.000000
[1] y[3][0] = 90.000000
[1] y[4][0] = 90.000000
[1] y[5][0] = 90.000000
[1] y[6][0] = 90.000000
[1] y[7][0] = 90.000000
[1] y[8][0] = 90.000000
[1] y[9][0] = 90.000000
[1] y[10][0] = 90.000000
[1] y[11][0] = 90.000000
[1] y[12][0] = 90.000000
[1] y[13][0] = 90.000000
[1] y[14][0] = 90.000000
[1] y[15][0] = 90.000000
[1] y[16][0] = 90.000000
[1] y[17][0] = 90.000000
[1] y[18][0] = 90.000000
[1] y[19][0] = 90.000000
[1] y[0][1] = 90.000000
[1] y[1][1] = 90.000000
[1] y[2][1] = 90.000000
[1] y[3][1] = 90.000000
[1] y[4][1] = 90.000000
[1] y[5][1] = 90.000000
[1] y[6][1] = 90.000000
[1] y[7][1] = 90.000000
[1] y[8][1] = 90.000000
[1] y[9][1] = 90.000000
[1] y[10][1] = 90.000000
[1] y[11][1] = 90.000000
[1] y[12][1] = 90.000000
…
…
‥(以下同様)

#bf
名前:
コメント:
最終更新:2009年06月20日 00:12
ツールボックス

下から選んでください:

新しいページを作成する
ヘルプ / FAQ もご覧ください。