#はじめに
Cythonとnumpyを使った時に、どうすれば高速化できるのか調べました。
##測定方法
大きさnのnumpyの配列を作り、その配列にn回代入と参照を行う関数func()と、
それをm回数実行するsum_func()を作りました。
今回は $n,m$ それぞれ $n=10000, m=100$ に設定しており、
sum_func()の実行時間を測定します。
これをjupyter notebookの「%timeit」を用いて測定しました。
Python
import numpy as np
def func1(n):
A = np.empty(n)
for i in range(n):
A[i] = i
sum = 0
for i in range(n):
sum += A[i]
return sum / n
def sum_func1(n):
res = 0
m = 10000
for i in range(n):
res += func1(m)
return res
###結果
%timeit sum_func1(100)
220 ms ± 3.31 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
結構遅いです。
##Cython(コンパイルだけ)
%%cython
import numpy as np
cimport numpy as np
cimport cython
def func2(n):
A = np.empty(n)
for i in range(n):
A[i] = i
sum = 0
for i in range(n):
sum += A[i]
return sum / float(n)
def sum_func2(n):
res = 0
m = 10000
for i in range(n):
res += func2(m)
return res
###結果
%timeit sum_func2(100)
176 ms ± 3.93 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
1.25倍くらい速くなりました。
##Cython(型指定)
%%cython
import numpy as np
cimport numpy as np
cimport cython
def func3(int n):
cdef int i
cdef double sum
cdef np.ndarray A = np.empty(n)
for i in range(n):
A[i] = i
sum = 0
for i in range(n):
sum += A[i]
return sum / float(n)
def sum_func3(int n):
cdef double res = 0
cdef int m = 10000
for i in range(n):
res += func3(m)
return res
###結果
%timeit sum_func3(100)
117 ms ± 2.55 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
ちょっとだけ速くなりました。
##Cython(numpyの詳細な型を指定)
%%cython
import numpy as np
cimport numpy as np
cimport cython
ctypedef np.float64_t DTYPE_t
def func4(int n):
cdef int i
cdef double sum
cdef np.ndarray[DTYPE_t, ndim=1] A
A = np.empty(n)
for i in range(n):
A[i] = i
sum = 0
for i in range(n):
sum += A[i]
return sum / float(n)
def sum_func4(int n):
cdef double res = 0
cdef int m = 10000
for i in range(n):
res += func4(m)
return res
###結果
%timeit sum_func4(100)
1.42 ms ± 4.45 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
numpyのデータタイプを指定すると、非常に速くなりました。
これが重要そうです。
##Cython(関数の返り値指定+cdef)
%%cython
import numpy as np
cimport numpy as np
cimport cython
ctypedef np.float64_t DTYPE_t
cdef double func5(int n):
cdef int i
cdef double sum
cdef np.ndarray[DTYPE_t, ndim=1] A
A = np.empty(n)
for i in range(n):
A[i] = i
sum = 0
for i in range(n):
sum += A[i]
return sum
cpdef double sum_func5(int n):
cdef double res = 0
cdef int m = 10000
for i in range(n):
res += func5(m)
return res
###結果
%timeit sum_func5(100)
1.34 ms ± 19.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
少しだけ速くなりました。
##Cython(関数のinline化)
%%cython
import numpy as np
cimport numpy as np
cimport cython
ctypedef np.float64_t DTYPE_t
cdef inline double func6(int n):
cdef int i
cdef double sum
cdef np.ndarray[DTYPE_t, ndim=1] A
A = np.empty(n)
for i in range(n):
A[i] = i
sum = 0
for i in range(n):
sum += A[i]
return sum
cpdef inline double sum_func6(int n):
cdef double res = 0
cdef int m = 10000
for i in range(n):
res += func6(m)
return res
###結果
%timeit sum_func6(100)
1.35 ms ± 30 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
ほとんど変わりません。
#比較
平均処理時間 | 改善倍率(倍) | |
---|---|---|
Python | 220 ms | 1.0 |
Cython(コンパイルだけ) | 176 ms | 1.25 |
Cython(型指定) | 117 ms | 1.88 |
Cython(numpyの詳細な型を指定) | 1.42 ms | 154 |
Cython(関数の返り値指定+cdef) | 1.34 ms | 164 |
Cython(関数のinline化) | 1.35 ms | 162 |
#まとめ
今回の測定の結果から、Cythonの高速に必要なことは
1.変数の型指定
2.numpyの詳細な型指定
3.関数の返り値を設定しcdefする
であるとわかりました。
#ソースコードについて
https://github.com/nena-undefined/til/blob/master/cython-numpy.ipynb