CythonでC++コードをラッピング
目次
はじめに
PythonはC++に比べて圧倒的に実行速度が遅いのですが、それでもPythonを使いたいと思うのはなんででしょうかね?
コーディングのしやすさや、何よりサードパーティのモジュールが豊富にあることですかね。
これがあったらいいなって言うのが大抵ありますね。Jupyterもあるし。
というわけで、今回はC++で作成したコード(具体的にはクラス)をCythonでラップしてPythonから使える様にしてみようと思います。
C++テストコード
先ずは、C++でクラスを作ります。前々回Cythonでテストをした時にも作りましたが、正数の累積和を求めるメンバ関数を持ったクラスを作ってみたいと思います。
ヘッダーファイルはこんな感じです。クラスの宣言部そのままです。cpp_test_sum_.h
という名前にしておきます。namespece
(7行目です)をつけてやるのがコツです。
最初の#ifndef
と最後の#endif
は、もしCPPTESTSUM_H
が定義されていなかったら、2行目の#define
で定義してそれ以降の記述を実行します。
逆にもし1行目時点で、CPPTESTSUM_H
が定義されていたら、それ以降は#endif
まで実行しません。
ヘッダーファイルの2重読み込みを防止するためのもので、C++でヘッダーファイルを書くときは慣例的に追加するみたいです。
メンバ関数の定義はファイルを分けておきます。cpp_test_sum_.cpp
という名前で保存します。
ヘッダーファイルcpp_test_sum_.h
を" "
で囲って# include
しておきます。今いるフォルダから# include
するという意味です。
ちなみに、今回に限らず、C++でライブラリを作成するときは、ヘッダーとメンバ関数の定義は、ファイルを分けて作成するのが普通みたいです。
Cythonラッパーコード
C++コードをラッピングするCythonコードを書いていきましょう。
以下、C++コードをラッピングするCythonコードです。cpp_test_sum.pyx
という名前で保存します。
注意点ですが、C++のコードはファイル名の後ろに_
を付けてCythonコードとは別の名前にしておきます。
1行目のfrom libcpp.vector cimport vector
は、C++で使えるvector
をインポートしています。libcpp
の中には、C++で使えるオブジェクトが入っているみたいです。他に確認したのは、string
等。必要に応じてインポートします。
3行目のcdef extern from "cpp_test_sum_.h" namespace "cpp_test_sum":
は、C++のヘッダーのインポートをしています。namespace
はC++ヘッダー内で定義している物を指定します。
4行目のcdef cppclass cppTestSum:
は、C++コード内のクラスをCython内にプロトタイプ宣言しています。
5行目のcppTestSum(int n_iter) except +
は、コンストラクターのプロトタイプ宣言です。except +
を後ろに付けるといいみたいです。(理由は何かのエラーの回避みたいです。)
7行目vector[double] sum
はメンバ変数sum
の宣言です。vector
のタイプは[ ]
で囲んで宣言します。C++の< >
から変えておく必要があります。
11行目~29行目までが、C++のクラスcppTestSum
をCythonのクラスTestSum
に置き換えている部分です。
C++のクラス名cppTestSum
は前にcpp
を付けて、Cython内で定義するのクラスTestSum
と名前が被らないようにしています。
12行目cdef cppTestSum* thisptr
では、C++クラスcppTestSum
をCythonクラスTestSum
内で使える変数としてthisptr
という名前でポインターで宣言しています。
14行目、17行目は、所謂コンストラクターとデストラクターにあたる部分です。コンストラクターでは、C++クラスcppTestSum
をnew
で生成してthisptr
に入れており、デストラクターでは、del
でthisptr
を解放しています。
20行目def calc_sum(self):
は、メンバ関数の定義です。単にC++クラスcppTestSum
のメンバ関数をreturn
で返しているだけです。
23行目~29行目がメンバ変数の定義です。property
で定義して、__get__
、__set__
関数で呼び出し、代入をすることで、通常のパブリック変数の様に.
で呼び出し、=
で代入ができるようになります。
コンパイル用setup.py
以下がC++コードcpp_test_sum_.cpp
をラッピングしてCythonコードcpp_test_sum.pyx
をコンパイルするsetup.py
です。
7行目のname = "cpp_test_sum",
と9行目のExtension("cpp_test_sum",
ですが、Cythonコードと同じ名前にしておきます。
10行目のsources=["cpp_test_sum.pyx", "cpp_test_sum_.cpp"],
は、Cythonコードと、C++コード名を入れておきます。
11行目のextra_compile_args=["-O3"],
は、g++のコンパイルオプションを指定しています。コンパイルでは自動で高速化オプションの-O
を付けてくれるみたいですが、一応-O3
オプションも付けておきます。
C++ヘッダーcpp_test_sum_.h
とクラス定義コードcpp_test_sum_.cpp
、またCythonラッピングコードcpp_test_sum.pyx
とsetup.py
を同じフォルダに入れて、以下のコマンドでコンパイルします。
python setup.py build_ext -i
コンパイルに成功すると、cpp_test_sum.pyd
ができます。Pythonスクリプト内でモジュールとしてインポートできます。
実行スピード比較
早速実行スピードを、Python、Cython、C++ラッパーで比較してみましょう。
前々回Cythonでテストをした時に作った、Pythonモジュールpy_test_sum.py
とCythonモジュールcy_test_sum.pyd
を、作成したC++ラッパーモジュールcpp_test_sum.pyd
と同じフォルダに入れて以下の実行スピード比較スクリプトを実行してください。
以下実行結果です。
Python Sum: 4.9999995e+13. Elapsed time: 14.8093574652 sec. Cython Sum: 4.9999995e+13. Elapsed time: 0.128290968028 sec. C++ Sum: 4.9999995e+13. Elapsed time: 0.0189597421757 sec.
C++はやっぱりすごいですね。Pythonの781倍、Cythonの6.8倍も速いですね。
C++で書いたRRLのコードをCythonでラッピング
前回C++で書いたRRLのコードをCythonでラップしてPythonから読めるようにしたいと思います。
スクリプトは長いのでGistを使うのはやめてGithubにアップしました。
今回の記事の内容は、上記のリポジトリの04_cython_cppwraperの中に全部入れました。
C++のヘッダーtradingrrl_.h
、メンバ関数定義部tradingrrl_.cpp
、Cythonラッピングコードtradingrrl.pyx
、コンパイル用setup.py
また、RRLの学習実行スクリプトmain.py
以外は全て2次的に生成されたものです。
ちなみに今回のテストコードは全てtest
フォルダに入っています。
python setup.py build_ext -i
でコンパイル後、python main.py
で学習が開始されます。
以下、main.py
の実行結果です。
Epoch loop start. Initial sharp's ratio is -0.0135567. Epoch: 100 / 10000. Shape's ratio: 0.0343914. Elapsed time: 0 sec. Epoch: 200 / 10000. Shape's ratio: 0.0554093. Elapsed time: 0 sec. Epoch: 300 / 10000. Shape's ratio: 0.0802484. Elapsed time: 0 sec. ... ... Epoch: 9800 / 10000. Shape's ratio: 0.169652. Elapsed time: 15 sec. Epoch: 9900 / 10000. Shape's ratio: 0.169963. Elapsed time: 15 sec. Epoch: 10000 / 10000. Shape's ratio: 0.169771. Elapsed time: 15 sec. Epoch loop end. Optimized sharp's ratio is 0.169771. Epoch: 10000 / 10000. Shape's ratio: 0.169771. Elapsed time: 15 sec.
C++の実行速度を保ちつつ、Pythonで実行出来ました!感無量ですね!