Raspberry Pi スパコン (7) 円周率


2023年 02月 23日

ノード間のデータの送受信

前回は、各ノードで計算した結果を表示しただけで、ノード間の送受信は行っていなかった。
今回は、それぞれのノードで計算した結果をマスターに集め、全体の平均値を求めることで、精度を上げてみよう。

まず、各ノードでの円周率の表示は止めて、マスターがワーカーからの値を受け取ったときに、どのワーカー(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()という関数を使ったのだが、今回はたまたまうまく行ったのだが、注意しないとデッドロックになり、プログラムが止まってしまうことがよくある。通信がからむと、タイミングも含めて全体の動きを把握しないと動きが分からなくなるが、それが通信の面白みでもあるし、それで挫折してしまう人も多いようだ。

そのあたりの事情については、徐々に説明していこうと思う。