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


2023年 05月 25日

ワーカーを終わらせよう

きちんと終わっていないが、問題は生成できているので、無理やりControl-Cで強制終了するという乱暴な方法もある。 しかし、プログラムを自動で動かす場合などは、どうしてもきちんと終了して欲しい。ということで、ちゃんと終わらせることを考えよう。

きちんと終了してくれないソースのワーカーの部分を再掲する。

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ループが永久に回り続けるようになっている。
問題を全部作り終えたら、それ以上はパターンデータがやってこないので、comm.recv(source=0, tag=1)は永久に待ってしまう。

問題を全部作り終えたら、「終了した」ということをマスターからワーカーに知らせれば、それを受け取ったワーカーは終了することができ、ワーカーもMPI.Finalize() を実行でき、MPIをきちんと終えることができるはずだから、ちゃんとコマンドプロンプト($)が表示されるはずだ。

ワーカー側の変更

そのために、worker()を以下のように変更した。
まず、「終了」のタグは9にしよう。
タグについてまとめると、

    1:パターン
    2:問題
    9:終了

という使い方になった。

受信するとき、どのタグでも受け取れるようにtag=MPI.ANY_TAGを指定した。

タグを値を調べる必要がでてきた(1か9)ので、MPI.Statusオブジェクトを作り、受信のときに、statusパラメータで渡す。すると、status.Get_tag()でタグの値が分かるので、パターン以外だったらwhileループを抜け出すようにした。

def worker():
    
	status = MPI.Status()
   	 
	while( True ):
    	prob = comm.recv(source=0, tag=MPI.ANY_TAG, status=status)
    	if status.Get_tag()!=1:
        	break
    	prob.retrycount = generator.generate(prob.pattern)
    	prob.problem = generator.getProblem()
       	 
    	comm.send(prob, dest=0, tag=2)

マスター側の変更

マスター側は、master()の中で、実際のパターンファイルの問題を読み出して全パターンに対する問題を生成する関数がgenerateNP(filename)である。

だから、この関数の中で、全問出来上がったところに、

comm.send(“END”, dest=1, tag=9)

を書き加えるだけで済む。

メッセージとして ”END”という文字列オブジェクトを与えているが、終了に関してはメッセージは無視されるので、実際は何でも良い。”END”と書いておくのが分かりやすいからそうしているだけである。

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()
   	 
	comm.send("END", dest=1, tag=9)
   	 
	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" )

動作確認

それでは、変更したプログラムで、500問を作ってみよう。

$ mpiexec -H RP0,RP1 python3 NP_MPIGEN1.py -g data/Pattern500.txt
No.1   H 18
- - - - X - - - -
- - - X - X - - -
X X - - - - X - -
- - X - - X - - -
- X - - - - - X -
- - - X - - X - -
- - X - - - - X X
- - - X - X - - -
- - - - X - - - -
[47]  24.297035 sec
- - - - 3 - - - -
- - - 6 - 2 - - -
8 7 - - - - 5 - -
- - 3 - - 7 - - -
- 4 - - - - - 1 -
- - - 5 - - 8 - -
- - 2 - - - - 3 4
- - - 8 - 5 - - -
- - - - 9 - - - -

中略

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 - -
[0]  0.131274 sec
- - 1 2 - - - - -
- - 5 - - 9 - - -
- - - - - 8 - 1 6
- 9 7 1 - 4 - - 2
- - - - - - - - -
1 - - 8 - 3 5 4 -
9 6 - 4 - - - - -
- - - 5 - - - - -
- - - - - 7 9 - -

total 500  failure 0

Time    1732.719763 sec

$

ワーカーの正常終了にこだわってきたが、正常に終わっていないということはリソースが無駄になっていて、こういうことが続くとシステム全体に無駄な負荷がかかって良くない。単独のプログラムなら、メインプログラムを止めればすべて止まるが、自動運転を行い、人が直接止めたりしないような場合には、リソースの浪費は色々な問題を引き起こすことになりかねないので注意しよう。

まだ並列処理になっていない

ここまでやってきたことは、マスター1、ワーカー1で、それも問題生成するのは1つのワーカーだけだったので、結局は逐次処理になっていた。これでは効率が上がらない。

複数のワーカーに違う問題を与えて、問題が出来たらマスターが受取り、次の問題をワーカーに送る(下図の右)ようにしたい。

次回から、本当の並列処理になるように変更を加えていこう。