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


2023年 06月 08日

複数ワーカーで処理

今までの機能追加で、複数ワーカーで問題生成を行う準備ができたので、これから複数ワーカーを使った場合の処理を考える。

複数のワーカーに問題を作らせると、早く作り終えるワーカーもあれば、なかなか作れないワーカー、作成に失敗するワーカーもある。
ファイルの順にパターンを送っても、問題が返ってくる順序はバラバラになるので、バラバラになっても良いような仕組みが必要だ。

マスター側

まず、パターンを何個ワーカーに送ったかと、問題(失敗を含めて)を受け取ったかをカウントしよう。そのための変数が以下の2つで、0に初期化しておく。

	sendCount = 0
	recvCount = 0

最初は、すべてのワーカーは問題が来るのを待っているので、全ワーカーに順に問題を送る。

	## 全ワーカーに問題を送りつける
	for i in range(1,size):
    	if sendCount<totalprobs:
        	comm.send(problems[sendCount], dest=i, tag=1)
        	sendCount += 1
    	else:
        	comm.send("END", dest=i, tag=9)

残りは、全パターンに対する問題を受取り終えるまでループする。
ループ内では、まず任意のワーカーから問題を受け取り(prob)、生成問題リストに加える。
statusを利用することで、送り元のワーカーを記憶しておく(workder)。

受け取った問題に対して処理をする前に、次の問題パターンを、暇になったワーカー(worker)に送りつける。
もし、もう全ての問題を送りつけ終えていたら、終了メッセージ(tag=9)を送る。

それから、受け取った問題(prob)の中身を見ながら、パターン、問題、などの情報を画面に出したり、指定されている出力ファイルに書き加えるが、この部分はほとんど変更がない。

作成したワーカーの値がprob.rankで取り出せるので、出来上がった問題のヘッダーの先頭に、rに続いてrankを表示している。

r1 [4]  1.627112 sec
- - - - 5 - - - -
- - - 6 - 3 - - -
3 7 - - - - 9 - -
- - 5 - - 2 - - -
- 8 - - - - - 6 -
- - - 3 - - 7 - -
- - 9 - - - - 5 6
- - - 7 - 4 - - -
- - - - 8 - - - -

以上説明したことが、マスター側の関数generateNP(filename)に書かれている。

def generateNP(filename):
	global datainput, dataoutput

	problems = util.readPatterns(filename)
	genproblems = []

	totalprobs = len(problems)
	failureCount=0
	successCount=0
	sendCount = 0
	recvCount = 0
    
	start_time = time.perf_counter()
    
	status = MPI.Status()

	## 全ワーカーに問題を送りつける
	for i in range(1,size):
    	if sendCount<totalprobs:
        	comm.send(problems[sendCount], dest=i, tag=1)
        	sendCount += 1
    	else:
        	comm.send("END", dest=i, tag=9)

	while recvCount < totalprobs:   	 
    	# パターンの送信、問題の受信
    	prob = comm.recv(source=MPI.ANY_SOURCE, tag=2, status=status)
    	recvCount += 1
    	worker = status.Get_source()
    	genproblems.append(prob)
   	 
    	if sendCount<totalprobs:
        	comm.send(problems[sendCount], dest=worker, tag=1)
        	sendCount += 1
    	else:
        	comm.send("END", dest=worker, tag=9)
   
    	str = f"No.{prob.index+1}   H {util.countHint(prob.pattern)}"
    	print(str)
    	if dataoutput != None:
        	print(str,file=dataoutput)
    	util.printHintBoard(prob.pattern)
   	 
    	if prob.retrycount >= 0:
        	if dataoutput != None:
            	util.printBoard(prob.problem,dataoutput)
        	print( f"r{prob.rank} [{prob.retrycount}]  {prob.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()
       	 
	print( f"length of genproblems = {len(genproblems)}" )
   	 
	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" )

ワーカー側

ワーカー側は変更しなくても動くのだが、どのワーカーで問題が作成されたかをProblemクラスのオブジェクトprobの中にrankを記憶するようにした。これで、マスター側でも、送られてきたprobの中のrankを参照することで、どのワーカー(rank)で問題が作られたかが分かる。

def worker():
    
	status = MPI.Status()
   	 
	while( True ):
    	prob = comm.recv(source=0, tag=MPI.ANY_TAG, status=status)
    	if status.Get_tag()!=1:
        	break
   	 
    	st_time = time.perf_counter()
    	prob.retrycount = generator.generate(prob.pattern)
    	prob.time = time.perf_counter() - st_time
    	prob.problem = generator.getProblem()
    	prob.rank = rank

    	comm.send( prob, dest=0, tag=2 )

実行

32ノード全てを使って実行してみた。
すると、No.3, No.32, No.11,,, の順番に表示された。
なお、問題の番号は、問題ファイルを読み込んだときに、Problemクラスのオブジェクトのメンバー indexに0から昇順に書き込まれているものを取り出して、+1して表示している。

$ cat host32
RP0 slots=4
RP1 slots=4
RP2 slots=4
RP3 slots=4
RP4 slots=4
RP5 slots=4
RP6 slots=4
RP7 slots=4
$ mpiexec -hostfile host32 python3 NP_MPIGEN4.py -g data/Pattern500.txt prob.txt
No.6   H 19
- - - X - - - X -
- - X - - - - X -
- - X - - - - - X
- - X - - X - - -
- X - - X - - X -
- - - X - - X - -
X - - - - - X - -
- X - - - - X - -
- X - - - X - - -
r6 [0]  1.860034 sec
- - - 3 - - - 8 -
- - 2 - - - - 4 -
- - 3 - - - - - 9
- - 6 - - 5 - - -
- 7 - - 9 - - 1 -
- - - 6 - - 7 - -
7 - - - - - 2 - -
- 4 - - - - 6 - -
- 1 - - - 4 - - -

No.10   H 19
- - X - - X - - -
- - X - - X - - X
- X - - - - - - X
X - - - - X - - -
- - - - X - - - -
- - - X - - - - X
X - - - - - - X -
X - - X - - X - -
- - - X - - X - -
r10 [0]  3.425711 sec
- - 2 - - 1 - - -
- - 5 - - 3 - - 1
- 4 - - - - - - 9
1 - - - - 2 - - -
- - - - 8 - - - -
- - - 9 - - - - 6
3 - - - - - - 2 -
7 - - 8 - - 4 - -
- - - 5 - - 3 - -

No.1   H 18
- - - - X - - - -
- - - X - X - - -
X X - - - - X - -
- - X - - X - - -
- X - - - - - X -
- - - X - - X - -
- - X - - - - X X
- - - X - X - - -
- - - - X - - - -
r1 [20]  5.866368 sec
- - - - 7 - - - -
- - - 6 - 5 - - -
3 9 - - - - 1 - -
- - 5 - - 6 - - -
- 2 - - - - - 1 -
- - - 3 - - 9 - -
- - 6 - - - - 7 5
- - - 1 - 9 - - -
- - - - 8 - - - -

以下省略

実行時の画面表示は、出来上がった問題から順に表示されているので、問題パターンの順番とは異なる。
出来上がった問題をprob.txtとして書き出したが、その中身はどうなっているだろうか。

$ cat prob.txt
No.6   H 19
- - - 3 - - - 8 -
- - 2 - - - - 4 -
- - 3 - - - - - 9
- - 6 - - 5 - - -
- 7 - - 9 - - 1 -
- - - 6 - - 7 - -
7 - - - - - 2 - -
- 4 - - - - 6 - -
- 1 - - - 4 - - -
No.10   H 19
- - 2 - - 1 - - -
- - 5 - - 3 - - 1
- 4 - - - - - - 9
1 - - - - 2 - - -
- - - - 8 - - - -
- - - 9 - - - - 6
3 - - - - - - 2 -
7 - - 8 - - 4 - -
- - - 5 - - 3 - -
No.1   H 18
- - - - 7 - - - -
- - - 6 - 5 - - -
3 9 - - - - 1 - -
- - 5 - - 6 - - -
- 2 - - - - - 1 -
- - - 3 - - 9 - -
- - 6 - - - - 7 5
- - - 1 - 9 - - -
- - - - 8 - - - -

以下省略

問題番号だけを表示すると、以下のようになっていた。

$ grep No prob.txt
No.6   H 19
No.10   H 19
No.1   H 18
No.34   H 20
No.35   H 20
No.36   H 20
No.2   H 18
No.26   H 20
No.37   H 20
No.39   H 20
No.22   H 20
No.3   H 18

以下省略

出来上がった問題ファイルの並び順も、マスターに問題が届いた順番になっていて、このままでは、入力したパターンの順番との対応付けが面倒である。

さて、どうしよう。