読者です 読者をやめる 読者になる 読者になる

UTALI

書き溜めておいた技術記事や旅行記のバックアップです。

Pythonが遅いので高速化したい - Cythonを使う

f:id:mochizuki_p:20161116202658p:plain

CythonでC言語・C++の外部ライブラリを利用する

Pythonは非常に生産性の高いスクリプト言語で、C言語やC++などのコンパイルが必要な静的型付き言語を使うのが面倒になってしまうほどです。

しかし、一つ問題があって、それはPythonで書かれたスクリプトの実行速度が、非常に遅いということです。その遅さは、C言語比で10倍〜100倍以上で、特に機械学習などの科学技術系計算でPythonを利用するときの致命的な欠点になります。

これについては、Cythonと呼ばれる仕組みを利用することで、この問題を回避することができます。それは、Pythonコードの中で巧妙に最適化されたネイティブコードを呼び出すことで、動的言語の生産性を生かしつつ、計算コストの高い箇所のみを、C言語・C++のライブラリを利用することで、場合によっては、すべてをネイティブコードで書いたプログラムに肉薄するほどの性能を発揮します。

もちろんCythonの持つポテンシャルを十分に発揮するためには、C言語で用いられるような静的型付けを指定するなどの適切な最適化が重要です。

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

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

パレートの法則

一般的に、プログラムの実行時間の80%は、コード全体の20%の部分によって費やされている。 つまり、ボトルネックになっている部分をネイティヴコードで置き換えることで大幅な実行時間の削減が期待できる。

何故Cythonは早いのか

Pythonはその柔軟性の代償として処理速度を犠牲にしています。例えば、Pythonは特に明示することなく、小さな桁から非常に大きな整数を扱うことができますが、これは背後でPythonのインタープリターが、自動的に型変換を実行しているからです。これはforループのような単純な処理を繰り返し行う場面では、大きくパフォーマンスを低下させます。このときC言語のようにプログラマの側が変数の型を指定すれば、暗黙の型変換を省略することができ、ループの速度が大幅に上昇することが予想されます。

今回は標準ライブラリのほかに、自作したものを含む外部ライブラリを利用するデモを実行します。

Cythonを利用する

pipがインストールされている場合は以下のコマンド一発でOK

pip3 install cython

CythonはC/C++を利用するので当然コンパイラのgccが必要です。もしインストールされていない場合はbrewでインストールしてから。Linuxの場合はデフォルトでgccが入っているはずです。

brew install gcc

Cythonパッケージを作成する

今回はPythonライブラリからimportすることで簡単に利用できるCythonパッケージを作成するデモを実行します。

Cythonパッケージの構成要素は主に3つで

  • .pyxという拡張子を持つ実装ファイル
  • .pxdという拡張子を持つ定義ファイル
  • setup.py

pyx・pxdのファイル名はライブラリと同じ名前にします。

pxdファイル

定義ファイル(pxd)は利用するC言語およびC++のライブラリをまとめて読み込み、外部から利用できるようにする箇所である。

C言語型の変数宣言

cdefを利用する。

cdef int i = 0

以下の形式でまとめて宣言することが可能

cdef:

     unsigned int index = 13464

     int f = 0

     double b = 0.0

構造体はstruct {構造体名}:に続けて、各要素を定義する

cdef struct body_t:

       double x[3]

       double y[3]

       double z[3]

C言語のライブラリ(オリジナルライブラリ)を利用したい

pxdファイルと同じディレクトリにヘッダおよび実装ファイルを配置して、ヘッダファイルをcdef extern fromで指定する。

cdef extern from "example.h":

つぎにctypedef structでCythonライブラリ全体を定義する構造体を宣言する。

test_stateという構造体を定義する。

ctypedef struct test_state

C言語の標準ライブラリを利用したい

libcから呼び出す。

mathのlog関数を利用したい
from libc.math import log

あとは普通にlogで呼び出すことができる。

log(2) 

C++の標準ライブラリを利用する

libcppから呼び出す

vectorのvector型を利用したい
from libcpp.vector cimport vector

あとは普通にvectorで変数を宣言できる

#int型のvectorを宣言
cdef vector[int] v1 

実装ファイル(pyx)

これはCythonコードをC言語の実行ファイルにコンパイルするために使うファイルです。 ここではpxdを構造体として読み込み、Pythonから利用できるメソッドとして利用できるように再定義を行います。 以下はpxdファイルで定義したメソッドをPythonから利用できるように再定義した例です。

cimport test_state

def rand():
    return test_state.example()

これによって コンパイル後に作成されたライブラリを読み込み。example()として利用することが可能です。

import test_state

test_state.example() 

Cythonライブラリの作成(setup.py)

ここからが本題で、実際にCythonパッケージをコンパイルして、任意のPythonコードから利用できるライブラリへと変換します。

もしC++を利用する場合は、setup.pyのExtensionにlanguage="c++"を指定することをお忘れなく。

from distutils.core import setup, Extension
import Cython.Build import cythonize

ext = Extension("test_state",

                sources=["test_state.pyx", "example.c"])

以下のコマンドを入力すると拡張子soのライブラリが作成されます。それをPythonコードと同一階層に配置することでCythonライブラリが利用できることになります。

python3 setup.py build_ext

ハイパフォーマンスPython

ハイパフォーマンスPython

C++の場合はこちらを参考に

www.utali.io