マスター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台、ワーカー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問全問成功)と実行時間も表示された。全問問題生成に成功するかどうかは乱数次第であるが、だいたいこんな感じになる。
だが、いつまで経っても正常終了しないようで、コマンドプロンプト($)が表示されない。なぜだろう。
この点は、次回で何とかしよう。