前回は、各ノードで計算した結果を表示しただけで、ノード間の送受信は行っていなかった。
今回は、それぞれのノードで計算した結果をマスターに集め、全体の平均値を求めることで、精度を上げてみよう。
まず、各ノードでの円周率の表示は止めて、マスターがワーカーからの値を受け取ったときに、どのワーカー(rank)からの値かを表示するようにした。
# -*- coding: utf-8 -*-
from mpi4py import MPI
import socket
import random
name = socket.gethostname()
comm = MPI.COMM_WORLD
rank = comm.Get_rank()
size = comm.Get_size()
def near_pi(n):
inside = 0
for i in range(n):
x = random.random()
y = random.random()
if x*x+y*y < 1.0:
inside += 1
return 4.0 * inside / n
def print_pi_from( r, pv ):
print( f"rank {r:2d} 円周率:{pv:.10f}" )
if __name__ == '__main__':
n = 100000000
pival = near_pi(n) # ノード別に円周率の近似値をもとめる
if rank!=0:
comm.send( pival, dest=0 )
else:
values = []
print_pi_from(0,pival)
values.append(pival)
for i in range(1,size):
pival = comm.recv( source=i )
print_pi_from(i,pival)
values.append(pival)
pifinal = sum(values)/size
print( "\nsize {:2d} 円周率:{:.10f}".format(size,pifinal) )
MPI.Finalize()
このプログラムでは、どのノードでも、いきなり1億回ループを回って、円周率の近似値を求める。
その後、ワーカー(rank!=0)では、comm.send関数により、送り先をマスターに指定して(dest=0)、求めた円周率の近似値(pival)を送る。
comm.send( pival, dest=0 )
一方、マスターの方では、すべてのノードの円周率近似値を入れるリストvaluesを用意し、まずマスターの結果を加える。
その後、ワーカー(rank)の1から順に、発信元のrankをsouceで指定して(source=i)、受け取る。
pival = comm.recv( source=i )
受け取った円周率の近似値をprintし、リストvaluesに加えている。
全ワーカーから円周率の近似値を集め終えたら、その平均値を求め、printしている。
実行すると、次のようになった。
$ time mpiexec -hostfile host32 python3 near_pi3.py
rank 0 円周率:3.1414633600
rank 1 円周率:3.1417578400
rank 2 円周率:3.1417908800
rank 3 円周率:3.1414866000
rank 4 円周率:3.1413850400
rank 5 円周率:3.1414530000
rank 6 円周率:3.1417467600
rank 7 円周率:3.1415944000
rank 8 円周率:3.1416952000
rank 9 円周率:3.1413570000
rank 10 円周率:3.1418284800
rank 11 円周率:3.1416416800
rank 12 円周率:3.1414983600
rank 13 円周率:3.1414805600
rank 14 円周率:3.1416725200
rank 15 円周率:3.1416013200
rank 16 円周率:3.1414264000
rank 17 円周率:3.1414621200
rank 18 円周率:3.1419564800
rank 19 円周率:3.1415575600
rank 20 円周率:3.1417105200
rank 21 円周率:3.1417377200
rank 22 円周率:3.1414764800
rank 23 円周率:3.1416972800
rank 24 円周率:3.1415203600
rank 25 円周率:3.1417297200
rank 26 円周率:3.1416506000
rank 27 円周率:3.1416384000
rank 28 円周率:3.1415647200
rank 29 円周率:3.1415687200
rank 30 円周率:3.1416558800
rank 31 円周率:3.1416114000
size 32 円周率:3.1416067925
real 3m30.555s
user 7m25.848s
sys 1m28.102s
円周率は、3.141592653589793 なので、誤差は1.4e-05=0.000014とかなり小さくなった。
ところで、データの送受信に、send()とrecv()という関数を使ったのだが、今回はたまたまうまく行ったのだが、注意しないとデッドロックになり、プログラムが止まってしまうことがよくある。通信がからむと、タイミングも含めて全体の動きを把握しないと動きが分からなくなるが、それが通信の面白みでもあるし、それで挫折してしまう人も多いようだ。
そのあたりの事情については、徐々に説明していこうと思う。