articles:numpy_vs_geo

numpyとgeo.pyの速度比較

ベクトル,回転行列,座標変換行列といった三次元幾何演算をpythonのリストをベースにgeo.pyというモジュールを自作している.リストベースの処理よりnumpyを使ったほうが高速なのではないかとの疑念もあるので比較を行う.
geo.pyはもともとpython2で開発されたが,単純な構造なのでpython3でも問題なく動く.

このテストは以下の条件で行った.

  • ProBook 474s
  • メモリ:8 GB
  • CPU:Core™ i5-3230M
  • OS: Ubuntu 20.04
  • jupyter notebook,python3

このipynb自身は,numpy_vs_geo.ipynbとなっている.

geo.py は自作モジュール

from geo import *
import time
import numpy as np
import pandas as pd

geo.pyには三次元ベクトル:VECTOR と三次元回転行列:MATRIXのクラスが定義されている. MATRIXのa, bはそれぞれx軸,y軸周りの回転を指定する.

g_v1=VECTOR(1,2,3)
g_v2=VECTOR(3,4,5)
g_R1=MATRIX(a=pi/3)
g_R2=MATRIX(b=pi/6)

この内容はこうなる.

print('g_v1 =', g_v1)
print('g_v2 =', g_v2)
print('g_R1 =', g_R1)
print('g_R2 =', g_R2)
g_v1 = v:[1.0, 2.0, 3.0]
g_v2 = v:[3.0, 4.0, 5.0]
g_R1 = m:[[1.0, 0.0, 0.0], [0.0, 0.5000000000000001, -0.8660254037844386], [0.0, 0.8660254037844386, 0.5000000000000001]]
g_R2 = m:[[0.8660254037844387, 0.0, 0.49999999999999994], [0.0, 1.0, 0.0], [-0.49999999999999994, 0.0, 0.8660254037844387]]

同様にnumpyのデータをndarrayで作る.

np_v1=np.array(g_v1)
np_v2=np.array(g_v2)
np_R1=np.array(g_R1)
np_R2=np.array(g_R2)

この内容はこうなる.

print('np_v1 =', np_v1)
print('np_v2 =', np_v2)
print('np_R1 =', np_R1)
print('np_R2 =', np_R2)
np_v1 = [1. 2. 3.]
np_v2 = [3. 4. 5.]
np_R1 = [[ 1.         0.         0.       ]
 [ 0.         0.5       -0.8660254]
 [ 0.         0.8660254  0.5      ]]
np_R2 = [[ 0.8660254  0.         0.5      ]
 [ 0.         1.         0.       ]
 [-0.5        0.         0.8660254]]

計測結果を入れる辞書の作成

def test(n,fn):
    i=0
    start=time.time()
    while i< n :
        fn()
        i += 1
    end = time.time()
    rslt=end-start
    return rslt
data = []
def judge(test_name, g_time, np_time) :
    if g_time < np_time :
        judgment = "geo.pyの勝ち"
    elif g_time > np_time :
        judgment = "npの勝ち"
    else :
        judgment = "引き分け"
    return test_name, g_time, np_time, judgment
test(100, lambda : g_v1+g_v2)
0.00030612945556640625
test(1000, lambda : g_v1+g_v2)
0.0015869140625
test(10000, lambda : g_v1+g_v2)
0.024413347244262695
test(100000, lambda : g_v1+g_v2)
0.1378471851348877
test(1000000, lambda : g_v1+g_v2)
1.200444221496582
test(10000000, lambda : g_v1+g_v2)
11.367036819458008
test(100000000, lambda : g_v1+g_v2)
112.89321899414062

百万回ぐらいでループ前後のオーバーヘッドの影響が少なくなってきている. まだ多少影響はあるが,一千万,一億は時間がかかるし, どうせループ内の処理の影響は消せないので百万回に決定する.

N=1000000
g_v1+g_v2
v:[4.0, 6.0, 8.0]
np_v1+np_v2
array([4., 6., 8.])
g_time = test(N, lambda : g_v1+g_v2)
print(g_time)
1.143357515335083
np_time = test(N, lambda : np_v1+np_v2)
print(np_time)
0.6900453567504883
data.append(judge('ベクトルの和', g_time, np_time))
g_v1.dot(g_v2)
26.0
np.dot(np_v1,np_v2)
26.0
g_time = test(N, lambda : g_v1.dot(g_v2))
print(g_time)
0.6457569599151611
np_time = test(N, lambda : np.dot(np_v1,np_v2))
print(np_time)
1.7959060668945312
data.append(judge('ベクトルの内積', g_time, np_time))

というか np がひどすぎる

g_v1*g_v2
v:[-2.0, 4.0, -2.0]
np.cross(np_v1,np_v2)
array([-2.,  4., -2.])
g_time = test(N, lambda : g_v1*g_v2)
print(g_time)
1.6717863082885742
np_time = test(N, lambda : np.cross(np_v1,np_v2))
print(np_time)
54.95365524291992
data.append(judge('ベクトルの外積', g_time, np_time))
g_R1*g_v1
v:[1.0, -1.5980762113533158, 3.2320508075688776]
np.dot(np_R1, np_v1)
array([ 1.        , -1.59807621,  3.23205081])
g_time = test(N, lambda : g_R1*g_v1)
print(g_time)
2.362830638885498
np_time = test(N, lambda : np.dot(np_R1,np_v1))
print(np_time)
1.848921537399292
data.append(judge('行列とベクトルの積', g_time, np_time))
g_R1*g_R2
m:[[0.8660254037844387, 0.0, 0.49999999999999994], [0.43301270189221924, 0.5000000000000001, -0.75], [-0.25, 0.8660254037844386, 0.43301270189221946]]
np.dot(np_R1,np_R2)
array([[ 0.8660254,  0.       ,  0.5      ],
       [ 0.4330127,  0.5      , -0.75     ],
       [-0.25     ,  0.8660254,  0.4330127]])
g_time = test(1000000, lambda : g_R1*g_R2)
print(g_time)
5.8199920654296875
np_time = test(1000000, lambda : np.dot(np_R1,np_R2))
print(np_time)
2.266876697540283
data.append(judge('行列同士の積', g_time, np_time))

まとめの表

df = pd.DataFrame(data, columns=["項目", "geo.py", "np", "結果"])
df

<div> <style scoped>

  .dataframe tbody tr th:only-of-type {
      vertical-align: middle;
  }
  .dataframe tbody tr th {
      vertical-align: top;
  }
  .dataframe thead th {
      text-align: right;
  }

</style>

<thead> <tr style=“text-align: right;”> <th> </th> <th> 項目 </th> <th> geo.py </th> <th> np </th> <th> 結果 </th> </tr> </thead> <tbody> <tr> <th> 0 </th> <td> ベクトルの和 </td> <td> 1.143358 </td> <td> 0.690045 </td> <td> npの勝ち </td> </tr> <tr> <th> </th> <td> ベクトルの内積 </td> <td> 0.645757 </td> <td> 1.795906 </td> <td> geo.pyの勝ち </td> </tr> <tr> <th> 2 </th> <td> ベクトルの外積 <HTML> </td> <td> 1.671786 </td> <td> 54.953655 </td> <td> geo.pyの勝ち </td> </tr> <tr> <th> 3 </th> <td> 行列とベクトルの積 </td> <td> 2.362831 </td> <td> 1.848922 </td> <td> npの勝ち </td> </tr> <tr> <th> 4 </th> <td> 行列同士の積 </td> <td> 5.819992 </td> <td> 2.266877 </td> <td> npの勝ち </td> </tr> </tbody>

</div> </HTML>

結論から言うと,意外にに差がないということが分かる. 大きなサイズのデータを扱うときはnumpyが良いのだろうが,三次元のベクトルや行列では大きな差は出ない.

それ以上に大きな驚きはnumpyのベクトルの外積の遅さであった.

ロボットのプログラムで使うときは個々の要素へのアクセスも多くあるので,なおさら差が出にくく現状のgeo.pyで十分であると考えられる.

Table 2: まとめの表
項目 geo.py np 結果
foo bar
  • articles/numpy_vs_geo.txt
  • 最終更新: 2024/03/28 14:43
  • by Takashi Suehiro