実験コードをCythonでゴリゴリ実装したら つらかった話
でっかい実験コードをCythonで書いて死んだ話です。
記憶を便りに書いているので間違っていたことを書いてたら指摘してください。
tl;dr
なぜCythonを使った
理由はだいたいこんなかんじです。
使いたいライブラリがC++でデータセットを.npy
と.pickle
で整備してあったので、
c++からboost.python
叩くか・CythonでWrapper書いて叩くか、の二択でした。
これまでどおりjupyterから実験するならCythonでwrapperじゃろ、ということで使ったかんじです。
(ちなみにjupyter要素ここまでです。)
なにがつらかった
つらかったことはこんなかんじです。
- Cython書きにくい
- Cythonから使えるC++標準ライブラリが少ない
- (Macだと
Symbol not found
が出る)- これはXCodeが悪かったのでCythonじゃなかったのですが、Cythonと切り分けができなかったのでかきました
- http://cocu.hatenablog.com/entry/2016/01/20/231108
Cython書きにくい
->
がないconfig_ptr.load()
みたいに.
を->
みたいにつかえるためない- shared_ptrだと、
.
が使えない
- template argumentに数字が使えない
Bitset<3>
みたいなやつがだめ
- 継承するごとにinterfaceをかかないといけない
- auto がない
- 人間に理解不能なエラーをgccが吐く
->
がない
たとえば革命的なshared_ptr
を使いたい時
(Cythonだとtemplateは[type]
で指定します。)
from libcpp.memory cimport shared_ptr from cython.operator cimport dereference cdef class Converter: cdef shared_ptr[Config] config def __cinit__(self): self.config = shared_ptr[Config]() # <- nullptr def __init__(self, path): self.config = shared_ptr[Config](new Config()) self.config->load(path) # エラー! self.config.load(path) # エラー! (*self.config).load(path) # なんでかエラー!!! dereference(self.config).load(path) # ok!
shared_ptrに(たしか)methodが生えてるので、それと区別するために->
が使いたいのですが、 Cythonの言語仕様にはありません 。
dereference
とかいう謎関数を噛ませてる必要があります。
ながいのでderef
とかにしてますが、よく忘れてエラります。
from cython.operator cimport dereference as deref
継承するごとにinterfaceをかかないといけない
Cythonは外部にあるコードのinterfaceを記述しますが、継承がうまくうごいてません 。
なので使うインターフェイス 全部を型も含めて記述しないといけません
cdef extern from "hoge.h" namespace "ninja": cdef cppclass BaseNinja: BaseNinja() int call(Youjo) bool operator== (BaseNinja) bool operator== (HiwaiNinja)# ←書かないと叩けない!!! cdef cppclass HiwaiNinja: int call(Youjo) # ←書かないと叩けない!!! bool operator== (BaseNinja)# ←書かないと叩けない!!! bool operator== (HiwaiNinja)# ←書かないと叩けない!!!
こんな言語で大きいコード書けるわけがない
Cythonから使えるC++標準ライブラリが少ない
Cythonでimport libcppしてるのはここにあります。
基本的に 標準ライブラリはこのなかに有るものしか使えなくて 、
使いたいものは 自分でinterfaceを書く必要 があります。
(言語互換にする宿命かもしれないですけど)
実験中にboostのdynamic bitsetを叩く必要があったので、自分で.hpp
よみながらinterfaceを書いたりしました。
どう解決した
Cythonの層を限りなく薄くしました
基本実装は全部C++にして、Pythonから扱いやすいdouble
とかint
を出力する関数を C++で 書き、
Cythonはその関数を叩いてPythonオブジェクトに変換するだけにしました。
そうするとSAN値が減らなくなりました。
今回の知見
Cythonは、PythonからC++(or C)のオブジェクトを叩く
というグルー言語
としては非常に有用です。
しかし、グルー言語でガチ実装*1しようとすると私みたいに死にます。
適材適所だなって実感しました。
ちなみに、CythonとC++で書いたコードで卒論はいい感じになりました。
*1:SAT solverを実装するとか