今までの機能追加で、複数ワーカーで問題生成を行う準備ができたので、これから複数ワーカーを使った場合の処理を考える。
複数のワーカーに問題を作らせると、早く作り終えるワーカーもあれば、なかなか作れないワーカー、作成に失敗するワーカーもある。
ファイルの順にパターンを送っても、問題が返ってくる順序はバラバラになるので、バラバラになっても良いような仕組みが必要だ。
まず、パターンを何個ワーカーに送ったかと、問題(失敗を含めて)を受け取ったかをカウントしよう。そのための変数が以下の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
以下省略
出来上がった問題ファイルの並び順も、マスターに問題が届いた順番になっていて、このままでは、入力したパターンの順番との対応付けが面倒である。
さて、どうしよう。