Raspberry Pi スパコン (13) ナンプレ


2023年 05月 09日

MPIの決まりごと

マスター1台、ワーカー複数台構成で実行するのが普通であるが、まずはマスター1台、ワーカー1台の構成を考える。
物事は、もっとも単純な場合から手をつけるべき。

プログラムの変更を始めよう。
まず、MPIを利用するために必ず行うことを最初に書く。

NP_GEN.pyを徐々に書き換えていくので、ファイル名をNP_MPIGEN?.pyとした。

?の部分には、0から順に数字が増えていく。

from mpi4py import MPI

comm = MPI.COMM_WORLD
rank = comm.Get_rank()
size = comm.Get_size()

NP_MPIGEN0.py がコマンドレベルから起動されると、以下のif判定がTrueとなり、ifの中身が実行される。

if __name__ == "__main__":
	if( rank==0 ):
    	master(sys.argv)
   	 
    	if datainput != None:
        	datainput.close()
    	if dataoutput != None:
        	dataoutput.close()  
	else:
    	worker()
   	 
	MPI.Finalize()

rank==0 の場合、マスターとしての動きとなり、引数をそのまま引き連れて関数masterを呼び出す。そうでない場合、ワーカーとして動くように、関数workerが呼び出される。

マスター/ワーカーにかかわらず、処理が終わったら MPI.Finalize()を呼び出してきちんとMPIを終える。

ワーカー

関数worker()はとてもシンプルである。

def worker():    
	while( True ):
    	prob = comm.recv(source=0, tag=1)
    	prob.retrycount = generator.generate(prob.pattern)
    	prob.problem = generator.getProblem()
       	 
    	comm.send(prob, dest=0, tag=2)

while(True)で永久ループを回り続ける。

マスターから(source=0)のパターン・メッセージ(tag=1)を受取り、probオブジェクトとする。
probの中の問題パターン(prob.pattern)をgenerator.generate()に渡して、問題の自動生成を行う。そのとき、再試行数が返ってくるので、prob.retrycountに書き込む。
さらに、出来上がった問題を取り出し、prob.problemに入れる。

以上の情報が加えられたprobを、問題メッセージ(tag=2)のタグで、マスターに送り返す(dest=0)。

whileループを繰り返すことで、次の問題を受け取っては、問題を生成し、出来た問題を返送することが延々と繰り返される。

マスター

def master(args):
	global datainput, dataoutput

	if len(args) < 3:
    	printErrorMessage()
    	return

	try:
    	datainput = open(args[2],'r')
    	if len(args)>=4:
        	dataoutput = open(args[3],'w')
	except:
    	print( '*** File open error ***')
    	return
    
	generateNP(args[2])

master()は、引数の正当性を確認し、駄目ならエラーメッセージを出し、また引数が不足していれば入力フォーマットを示して終了する。

最後に、与えられたパターンファイルを読み込んで延々と問題を作る関数generateNPを呼び出す。

複数問題の生成

def generateNP(filename):
	global datainput, dataoutput

	problems = util.readPatterns(filename)

	totalprobs = len(problems)
	failureCount=0
	successCount=0
	n = 0
    
	start_time = time.perf_counter()

	for pb in problems:
    	n+=1
    	str = f"No.{n}   H {util.countHint(pb.pattern)}"
    	print(str)
    	if dataoutput != None:
        	print(str,file=dataoutput)
    	util.printHintBoard(pb.pattern)
   	 
    	st_time = time.perf_counter()
    	comm.send(pb, dest=1, tag=1)
    	prob = comm.recv(source=1, tag=2)
    	gen_time = time.perf_counter() - st_time
   	 
    	if prob.retrycount >= 0:
        	if dataoutput != None:
            	util.printBoard(prob.problem,dataoutput)
        	print( f"[{prob.retrycount}]  {gen_time:06f} sec" )
        	util.printBoard(prob.problem)
        	successCount += 1
    	else:
        	str = f"FAILURE {-prob.retrycount}"
        	if dataoutput != None:
            	print(str,file=dataoutput)
        	print(str)
        	failureCount += 1
    	print()
   	 
	exe_time = time.perf_counter() - start_time

	probSize = len(problems)
	totalCount = successCount+failureCount
	print( f"total {totalCount}  failure {failureCount}\n" )
	print( f"Time\t{exe_time:06f} sec\n" )

プログラムはやや長くなっているが、MPI対応のために加えた23行、24行の2行だけが本質的である

やっていることは、

  1. パターンが入っているProblemクラスのオブジェクトpbをワーカーに送る(dest=1)。
  2. 生成された問題が入っているProblemクラスのオブジェクトを、ワーカーから受取り(source=1)、probとする。

だけである。長くなっているのは、時間計測をしたり、メッセージを表示したりする処理なので、説明は省略する。

マスター1台、ワーカー1台の場合、実は並列動作をしている部分はまったくなく、マスターはワーカーが返事を返してくるまでじっと待っている。

動作確認

修正し終えたら、実行しよう。

ホストの指定は、マスター1台、ワーカー1台なので、 -H RP0,RP1  とした。

$ mpiexec -H RP0,RP1 python3 NP_MPIGEN0.py -g data/Pattern500.txt

    中略

No.500   H 23
- - X X - - - - -
- - X - - X - - -
- - - - - X - X X
- X X X - X - - X
- - - - - - - - -
X - - X - X X X -
X X - X - - - - -
- - - X - - - - -
- - - - - X X - -
[1]  0.525617 sec
- - 9 3 - - - - -
- - 5 - - 1 - - -
- - - - - 8 - 6 4
- 8 1 9 - 5 - - 2
- - - - - - - - -
5 - - 8 - 3 7 4 -
4 2 - 5 - - - - -
- - - 6 - - - - -
- - - - - 7 3 - -

total 500  failure 0

Time    986.649639 sec

ちゃんと実行し、最後に生成結果(500問全問成功)と実行時間も表示された。全問問題生成に成功するかどうかは乱数次第であるが、だいたいこんな感じになる。

だが、いつまで経っても正常終了しないようで、コマンドプロンプト($)が表示されない。なぜだろう。

この点は、次回で何とかしよう。