リスト8-1にNumPy,SciPy,CuPyを用いて連立一次方程式を解くソースコードを示します。
NumPyとSciPyはCPU用、CuPyはGPU用です。
いずれもlinalg.solve関数を使用しています。
実数と複素数、単精度と倍精度を選択することができます。
行列は乱数で作成した非対称密行列です。
CuPyではデバイスメモリーを確実に解放するためにmemory poolを使用しています。
リスト8-1 NumP,SciPy,CuPyを用いて連立一次方程式を解くソースコード
(leq.py)
"""
leq.py
solve linear equations
NumPy, SciPy, CuPy
"""
import numpy as np
import scipy as sp
import cupy as cp
import time
# parameters
N = 5000 # matrix size
L = 1 # repeat
dkind = 1 # 1=real, 2=complex
solver = 1 # 1=NumPy, 2=SciPy, 3=CuPy
precision = 1 # 1=single, 2=double
# dtype
f_dtype = 'f4' if precision == 1 else 'f8'
c_dtype = 'c8' if precision == 1 else 'c16'
# timer
t0 = time.time()
# real
if dkind == 1:
a = np.random.rand(N, N).astype(f_dtype)
b = np.random.rand(N).astype(f_dtype)
x = np.zeros(N, f_dtype)
# complex
elif dkind == 2:
a = np.random.rand(N, N).astype(f_dtype) \
+ np.random.rand(N, N).astype(f_dtype) * 1j
b = np.random.rand(N).astype(f_dtype) \
+ np.random.rand(N).astype(f_dtype) * 1j
x = np.zeros(N, c_dtype)
# well-condition
for i in range(N):
a[i, i] *= 10
# alloc device memory
if solver == 3:
mempool = cp.get_default_memory_pool()
# copy to device
d_a = cp.array(a)
d_b = cp.array(b)
d_x = cp.array(x)
# timer
t1 = time.time()
# solve
for _ in range(L):
if solver == 1:
# NumPy (CPU)
x = np.linalg.solve(a, b)
elif solver == 2:
# SciPy (CPU)
x = sp.linalg.solve(a, b)
elif solver == 3:
# CuPy (GPU)
d_x = cp.linalg.solve(d_a, d_b)
# timer
t2 = time.time()
# free device memory
if solver == 3:
# copy to host
x = cp.asnumpy(d_x)
# free
d_a = None
d_b = None
d_x = None
mempool.free_all_blocks()
# check
res = np.dot(a, x) - b
ans = np.linalg.norm(res)
# output
device = 'CPU(NumPy)' if solver == 1 else 'CPU(SciPy)' if solver == 2 else 'GPU(CuPy)'
print('%s N=%d(%d), %.2f+%.2f[sec], err=%.3e'
% (device, N, L, t1 - t0, (t2 - t1) / L, ans))
# free
a = None
b = None
x = None
表8-1に実数の連立一次方程式の計算時間を示します。
計算時間を正確に測定するために、
計算時間が短いときは多数回計算してその回数で割っています。
| 行列の大きさN | CPU (NumPy) | CPU (SciPy) | GPU (CuPy) | |||
|---|---|---|---|---|---|---|
| 単精度 | 倍精度 | 単精度 | 倍精度 | 単精度 | 倍精度 | |
| 5000 | 0.42秒 | 0.38秒 | 0.31秒 | 0.51秒 | 0.03秒 | 0.45秒 |
| 10000 | 2.29秒 | 2.16秒 | 1.41秒 | 2.56秒 | 0.16秒 | 3.39秒 |
| 20000 | 16.56秒 | 14.79秒 | 11.69秒 | 22.17秒 | 0.95秒 | 26.83秒 |
表8-1から以下のことがわかります。
以上から、連立一次方程式を解くときの指針は以下のようになります。
表8-2に複素数の連立一次方程式の計算時間を示します。
表8-1と同じ傾向が見られます。
複素数の計算時間は実数の3~5倍になります。
| 行列の大きさN | CPU (NumPy) | CPU (SciPy) | GPU (CuPy) | |||
|---|---|---|---|---|---|---|
| 単精度 | 倍精度 | 単精度 | 倍精度 | 単精度 | 倍精度 | |
| 5000 | 1.03秒 | 0.96秒 | 0.79秒 | 1.56秒 | 0.07秒 | 1.52秒 |
| 10000 | 8.75秒 | 6.75秒 | 4.38秒 | 9.10秒 | 0.39秒 | 12.25秒 |
| 20000 | 90.45秒 | 127.64秒 | 34.36秒 | 115.87秒 | 2.95秒 | メモリー不足 |