Into the Horizon

programming, photography, and daily log

swigでpythonからc++コードを使う

c++pythonの連携はどうやるのか、ずっと気になってたのでメモ。
もともとはpythonのsubprocessモジュールを使って無理矢理連携させてたりしていたのですが、色々難しい&結局満足にできないのでオススメしません…


■どうやれば連携できるの?
こちら(http://d.hatena.ne.jp/niitsuma/20080209/1203184397)に色々書かれています。
今回はswigを使ってみます。


swigを使ってみる
まずはswigをインストール。特に問題なくインストールできました。
本家ページ:http://www.swig.org/

次に、本家のpython用マニュアルに従ってサンプルを動かしてみる。(http://www.swig.org/Doc1.3/Python.html
そのまんまコピペで………あれ、いかない。

$ swig -python example.i
$ python setup.py build_ext --inplace
running build_ext
building '_example' extension
/usr/bin/gcc-4.2 -fno-strict-aliasing -fno-common -dynamic -pipe -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -I/opt/local/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7 -c example_wrap.c -o build/temp.macosx-10.6-x86_64-2.7/example_wrap.o
unable to execute /usr/bin/gcc-4.2: No such file or directory
error: command '/usr/bin/gcc-4.2' failed with exit status 1

使ってるgccは4.6なので、コンパイラ選びが間違ってるみたいです。CC=g++とか書けばいいのかな…?

$ CC=g++ CXX=CC python setup.py build_ext --inplace
~略(さっきより進んだ)
unable to execute /usr/bin/gcc-4.2: No such file or directory
error: command '/usr/bin/gcc-4.2' failed with exit status 1

途中からgcc-4.2をみにいってるorz
どういうことだ………

色々やってみるも、なかなか上手くいかず、挫折しそうになる…………
最初はswig周りを頑張って調べていたものの、考えてみればこの段階ではpythonのdistutilsを使ってモジュールをインストールしようとしてるだけなので、distutils周りを調べればいいことに気付く。
正直未だにしっかり理解はしてないけど、なんかsetup.pyに以下のように追記すればいいことを発見。(雑すぎる…

from distutils import sysconfig
print sysconfig.get_config_var("LDSHARED")
sysconfig._config_vars["LDSHARED"] = "/opt/local/bin/g++ -bundle -undefined dynamic_lookup -isysroot / -L/opt/local/lib"
print sysconfig.get_config_var("LDSHARED")

from distutils.core import setup, Extension
#以下サンプルと同じ

print文はデバッグのためもありますが、最初のprintをいれないと、LDSHAREDなんてねーよって怒られるので必要です。
この状態で、上のコマンドを実行…うむ、できた。
pythonからもモジュールを呼び出せました。


速度的なところが気になったので一応テスト。
・10^9回x++するだけのコードをcppで書いて、swigを使ってpythonから呼び出してみる
→実行時間は約3秒
・同じコードをpythonで書いて、pythonで呼び出してみる
→実行時間は300秒以上。当然だけど早くなってます。
・同じコードを、普通にcppで呼び出してみる
→実行時間は約5秒。あれ、pythonで呼びだした時より長い…………なぜ?

・入力を2倍して返すコードをcppで書いてみて、pythonから10^7回呼び出してみる
→実行時間は9秒ほど
・入力を2倍して返すコードをpythonで書いてみて、pythonから10^7回呼び出してみる
→実行時間は8秒ほど。何度もc++のコードを呼び出すよりも、ネイティブなコードを呼んだ方が早くなる場合もあるみたいですね。(参考:http://python.matrix.jp/tips/cpp_extension/


コンパイラ・リンカのフラグを弄る
c++の方でmecabを使わせて、それをごにょごにょしたのをpythonに渡したいので、フラグを弄ってc++側でmecabが使えるようにします。
http://www.python.jp/doc/2.5/inst/tweak-flags.html#SECTION000610000000000000000
ここにそれっぽいこと書いてあるんですが、よくわからん…
pythonで、help(Extension)とすると、それっぽいことが書いてあるので、これを参考にsetup.pyを書き直すとします。

geho_module = Extension('_geho',
                           sources=['geho_wrap.cxx', 'geho.cpp'],
                        include_dirs=['/opt/local/include'],
                        library_dirs=['/opt/local/lib'],
                        libraries=['mecab', 'stdc++'],
)

これでよし…と思ったけど、できないヽ(´o`;

ImportError: dlopen(./_geho.so, 2): Symbol not found: __ZN5MeCab12createTaggerEPKc
  Referenced from: /Users/katsuma/src/geho/_geho.so
  Expected in: flat namespace
 in /Users/katsuma/src/geho/_geho.so

ううむ……。
tagger->parseをしなければ(つまり、includeしてMeCab::Tagger *taggerするだけ)エラーは起きないようなので、一応書き方はあってるみたいなんですが……
ってこの症状、前にも経験した気がする。何をすれば直ったのだっけ……