LoginSignup
42
35

More than 5 years have passed since last update.

Cythonでnumpyを使った時の速度比較、高速化には何が必要か

Posted at

はじめに

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する

であるとわかりました。

ソースコードについて

42
35
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
42
35