Np-Urのデータ分析教室

オーブンソースデータなどWeb上から入手できるデータを用いて、RとPython両方使って分析した結果を書いていきます

Pythonで高速化処理!numbaとCythonの実行速度を比較してみた。

本記事は、python Advent Calendar 2017の23日目の記事です。


今回はPythonを高速化するための、numbaとCythonについて紹介します。

Pythonを使っている方なら、for文処理が遅い、データの前処理が終わらないといった状況に一度は陥ったことがあると思います。
そんなときの一つの対策手段として、今回紹介するnumbaやCythonがあります。

なお、今回の記事は「numba?Cython?何それおいしいの?」という人向けの記事なので、既に利用している方はあまり参考にならないかもしれません。

なお、以下のような流れで進めていきます。

  • numbaとCythonの説明、導入方法と使い方について
  • サンプルコードで速度比較

Cython ―Cとの融合によるPythonの高速化

Cython ―Cとの融合によるPythonの高速化

numba

それでは、早速numbaからやっていきましょう。

numbaとは...?

numbaとは、JIT(just-in-time)コンパイラを使ってPythonを高速化するモジュールになります。
JITコンパイラとは、名前の通り、スクリプト実行時にコンパイルも行って実行速度を上げるものになります。

C言語のようなコンパイラ言語は、書いたプログラムを一回コンパイルしてあげる必要がありますが、このnumbaはわざわざコンパイルをする必要がなく勝手にやってくれちゃいます。

普段通りにPythonのコードを書きつつ、ちょっとした味付けを施すだけで実行速度を上げることができる素晴らしいモジュールです。

詳しい使い方に関しては以下ドキュメントを参考にしてみてください。
numba 最新版0.36.1ドキュメント

インストール方法

以下のようにpipでインストールできます。

pip install numba

numpyのバージョンや依存ライブラリが複数あるので、もしエラーが出た方は、エラーで出た依存ライブラリの方もインストールしてみてください。

使い方

numbaの使い方ですが、本当に簡単です。
以下のように、関数の前に@jitをつけてあげるだけでOKです!

from numba import jit

@jit
def example():
      ~

上記の記法では、内部的に型推論を行って処理していますが、具体的な引数や戻り値の型がわかっている場合は指定することも可能です。

@jit(A(B1,B2,...))
def example(B1, B2, ...):
     ~
     return A

Aの部分で返り値の型指定、その次のB1,B2...以降で引数の型指定をすることが出来ます。

この後具体例をお見せしますが、これだけで十分に処理速度を早くすることができました。

Cython

続いてCythonの説明に入ります。

Cythonとは...?

PythonでC言語の拡張モジュールを作成する際に使用するプログラミング言語です。

Cythonでは、Python風に記述した関数をコンパイルすることや、C言語の関数の呼び出しが行えます。全部C言語で書くのはしんどいけど、できるだけPython記法で処理速度を上げたいという方にとって朗報となるのがCythonになります。

C言語の要素も入ってくるので、多少とっつきにくさもありますが、基本的にはPython記法でいけるので、そこまでストレスはないかと思います。

インストール方法

Mac os Xの方はXcodeを先に入れておいてください。
あとはpipを使うことでインストールできます。

pip install cython

使い方

詳細な使い方については、公式Cython ドキュメントを読むのが良いかと思いますが、簡単に説明すると流れとしては以下のような感じです。

  1. コンパイルしたい関数を書いたファイルを拡張子.pyxで保存
  2. setup.pyというビルド用ファイルを作る
  3. 「$ python setup.py build_ext --inplace」をコマンドライン上で実行

ステップ1でC言語的な記法を使うことや、ステップ2でsetup.pyの作成が必要になるので、少し大変な印象です。

実装に組み込むなどの場合は上記の方式を利用したほうがいいかもしれませんが、とりあえず使ってみたいという方は以下の方法もあります。

jupyter notebook上で使う

jupyter notebookを使っている方は、もっと簡単にCythonを使うことが出来ます。

%load_ext Cython

%%cython
def test(int a,b):
    cdef int i,n
    for i in range(a):
        n += i*b
    return n

%load_exr_Cythonで、jupyter notebook内でコンパイルを行えるようにしています。

あとは、jitの時と同じように、関数の前に%%cythonをつけてあげて、引数や関数内の変数の型の定義を行えば完了です。
関数内での変数の定義を行うにはcdefを使います。

jupyter notebook上でCythonを使えるので、インタラクティブにコーディングできる点がいいですね。

速度比較

さて、ここまででJIT(numba)とCythonの使い方がざっくりとは分かったので、実際に速度比較を行っていきましょう。

まず基本となるPythonのコードは以下のものです。

import time

def sum_py(x):
    sum_num = 0
    for i in range(x):
        sum_num += i
    return sum_num

starttime = time.time()
sum_py(100000001)
proc_time_python = time.time() - starttime
print('python', proc_time_python)

0~1億までの数字を全て足し合わせる関数になっています。

では、この関数をCythonとJIT仕様に書き換えてみます。
なおCythonはJupyter notebook上で利用しております。

from numba import jit

%load_ext Cython
%%cython
def sum_cy(int x):
    cdef int i
    cdef int sum_num
    sum_num = 0
    for i in range(x):
        sum_num += i
    return sum_num

@jit
def sum_jit(x):
    sum_num = 0
    for i in range(x):
        sum_num += i
    return sum_num

@jit('int64(int64)')
def sum_jit_sign(x):
    sum_num = 0
    for i in range(x):
        sum_num += i
    return sum_num

まず、上からCythonになりますが、jupyter上では%%cythonを記述することでコンパイルしてくれるんでしたね。

引数と返り値に関して、cdefで型を指定してあげています。
jitに関しては、型を推論する場合と指定してあげる2つのパターンを用意しました。

推論型は、@jitを関数に付けただけです。
型指定の方は、int64(int64)の部分で、()外が返り値、()内が引数の指定をしてます。

さて実際に速度比較をしてみた結果が以下の表になります。

実行時間(sec) pythonとの相対倍率
Python 9.46 1
Cython 6.79e-05 139,345
jit(型指定なし) 6.82e-05 138,732
jit(型指定あり) 6.7e-05 141,217
Pythonと比較すると、Cython,JITともに約14万倍の速さで計算しています!!
JITの型指定あり、なしを比べるとやや型指定している方が早いようですね。CythonとJIT間では、それほど変わらないといった結果でした。

ちょっと試しただけですが、Cython、numbaともに中々いいですね笑

今回かなり単純な処理での比較でしたので、今後より複雑な処理においてどうなるかというところを検証していきたいと思います。

終わりに

今回は、numbaとCythonのご紹介するとともに、実行速度比較を実際にやってみましたが、素人レベルの理解でもそれなりに実行速度を上げられることがわかりました。

今回CythonやNumbaを使ってみて、実用レベルでも使えるようになりたいなと思ったので、今後シリーズ化して記事にしていこうかと思ってます。
それでは、良いPython lifeを!!