目次

4. MPI for Python

4.1 MPIとは

MPI(Message Passing Interface)とは複数台のコンピュータで計算処理を分割し計算時間を短縮するものです。
実行単位をプロセスと呼びます。プロセス間の通信によりデータのやり取りを行います。
1台のコンピュータのマルチコアやマルチCPUで並列計算することもできます。

4.2 MPI for Python プログラミング

MPI for Python 関数
MPIの関数はたくさんありますが、表4-1に MPI for Python でよく使用する関数を載せます。 これだけの関数とその派生関数を理解しておけばたいていの実用プログラムができます。
なお、C版のMPIで必須であるInit関数とFinalize関数はありません。

表4-1 MPI for Python の関数の一部(クラス名 : mpi4py.MPI.Comm)
No.関数プロトタイプ機能
1Get_size()プロセスの数を取得します。必須です。
2Get_rank()自分のプロセス番号を取得します。必須です。
3Bcast(buf[, root])特定のプロセスからすべてのプロセスにデータを送信します。入力データを全プロセスで共有するときに使用します。集団通信
4Reduce(sendbuf, recvbuf[, op, root])すべてのプロセスのデータに指定した操作を加え結果を特定のプロセスに格納します。集団通信
5Send(buf, dest[, tag])指定したプロセスにデータを送信します。1対1通信
6Recv(buf[, source, tag, status])指定したプロセスからデータを受信します。1対1通信
7Sendrecv(sendbuf, recvbuf[, root])指定したプロセスと送信を受信を同時に行います。MPI_SendとMPI_Recvを同時に使ってデッドロックが発生するとき安全に通信できます。1対1通信
8Scatter(sendbuf, recvbuf[, root])指定した配列を分割して順に各プロセスに分配します。集団通信
9Gather(sendbuf, recvbuf[, root])各プロセスから集めたデータを一つの配列に格納します。集団通信
10Barrier()全プロセスの同期をとります。集団通信

4.3 MPI for Python によるベクトル内積の計算

リスト4-1に MPI for Python を用いてベクトル内積を並列計算するプログラムを示します。

リスト4-1 MPI for Python によるベクトル内積のソースコード (sdot_mpi.py)


"""
sdot_mpi.py
scalar product of two vectors
MPI for Python (mpi4py)
"""

import numpy as np
from numba import jit
from mpi4py import MPI

# (sdot-1) Numba
@jit(cache=True, nopython=True)
def sdot_numba(a, b):
    n = len(a)
    s = 0
    for i in range(n):
        s += a[i] * b[i]
    return s

# (sdot-2) for (very slow)
def sdot_for(a, b):
    n = len(a)
    s = 0
    for i in range(n):
        s += a[i] * b[i]
    return s

# parameters
N = 10000000
L = 1000
fn = 'sdot-1'
dtype = 'f4'  # 'f4' or 'f8'

# MPI
comm = MPI.COMM_WORLD
comm_size = comm.Get_size()
comm_rank = comm.Get_rank()

# timer
comm.Barrier()
t0 = MPI.Wtime()

# alloc
block = (N + (comm_size - 1)) // comm_size
i0 = (comm_rank + 0) * block
i1 = (comm_rank + 1) * block
i1 = min(i1, N)
a = np.zeros(i1 - i0, dtype=dtype)
b = np.zeros(i1 - i0, dtype=dtype)

# setup problem
a[:] = i0 + np.arange(i1 - i0)
b[:] = a[:]

# timer
comm.Barrier()
t1 = MPI.Wtime()

# calculation
s = np.zeros(1, dtype=dtype)
for _ in range(L):
    if   fn == 'sdot-1':
        l_s = sdot_numba(a, b)
    elif fn == 'sdot-2':
        l_s = sdot_for(a, b)
    l_s = np.array(l_s).astype(dtype)
    comm.Reduce(l_s, s)

# timer
comm.Barrier()
t2 = MPI.Wtime()

# output
if comm_rank == 0:
    exact = N * (N - 1) * (2 * N - 1) / 6
    print('(%s) N=%d, L=%d, Np=%d' % (fn, N, L, comm_size))
    print('%.2f+%.2f[sec], %e, %e' % (t1 - t0, t2 - t1, s[0], exact))

# free
a = None
b = None

4.4 MPI for Python プログラムの実行法

Windowsで MPI for Python プログラムを実行するには、 Microsoft MPI [8]をインストールする必要があります。
その後、Windowsターミナルを起動して、sdot_mpi.py ファイルがあるフォルダに移動した後、 以下のコマンドを実行します。

> mpiexec.exe -n 8 python.exe sdot_mpi.py (数字はプロセス数)

4.5 MPI for Python の計算時間

表4-2と図4-1に MPI for Python の計算時間を示します。
配列の大きさ(=N)と繰り返し回数(=L)の積は一定(=1010)です。 従って全体の演算量は同じです。

表4-2 MPI for Python によるベクトル内積の計算時間(実数単精度、()内は1プロセスとの速度比)
No.配列の大きさN繰り返し回数L1プロセス2プロセス4プロセス8プロセス16プロセス
110,000,0001,0006.22秒(1.0)3.20秒(1.94)1.75秒(3.55)1.64秒(3.79)1.72秒(3.62)
21,000,00010,0006.19秒(1.0)3.21秒(1.93)1.76秒(3.52)1.71秒(3.62)1.93秒(3.21)
3100,000100,000 6.19秒(1.0)3.22秒(1.92)1.77秒(3.50)1.63秒(3.80)1.82秒(3.40)
410,0001,000,0006.18秒(1.0)3.22秒(1.92)1.75秒(3.53)1.66秒(3.72)1.77秒(3.49)

図4-1 MPI for Python によるベクトル内積の計算時間(実数単精度)

表4-2と図4-1から以下のことがわかります。