Pythonでネイティブコードを簡単に呼ぶ方法として、ctypesとboost.pythonがある。ctypesは最近はPython本体に含まれるし、boost.pythonもBoost本体に含まれているようだ。この2つは、swigや自力でインタフェースを書く場合と比べてだいぶ楽に呼び出せる。
で、どのくらいコストが違うのか、試してみようと。ネイティブコードを呼んだ先では大差ないと思うけど、ネイティブコード呼び出しにかかるコストによっては、インタフェースの粒度を考えないといけなくなる。
そこで、ためしに
- 何もしない
- math.sinを呼ぶ
- cmath.sinを呼ぶ
- boost.python経由でstd::sinを呼ぶ
- ctypesでlibmのsinを呼ぶ
という呼び出しの速度を測ってみた。cmathは複素数に対応した数学関数のパッケージです。sinが複素数を返すことはない…と思いますけど、mathとcmathの比較のために一応。200万回の呼び出して時間を測定して平均した。測定環境がVista(32bit)環境のVMWareの中のFedora8(x68_64)環境、というのがややこしい。こういう環境は測定が正確じゃないかもしれないですね。仮想環境のgettimeofdayはあんまり信用できないような気がする。vmware-toolsを入れて時刻をホストと同期はさせてますが。
測定 | 呼び出しに要した時間(us) |
何もしない | 0.535881 |
math.sin | 0.505848 |
cmath.sin | 0.632371 |
boost.python経由でstd::sin | 0.541841 |
ctypesでlibmのsin | 1.307253 |
何もしない、というのは測定したいもの以外の時間(random.randomを読んだりループを回したりしているんで…)だけど、math.sinが異常に高速で、何もしないよりも短い時間になってしまった。それでも「何もしない」を引くと、次のように。
測定 | 呼び出しに要した時間(us) |
math.sin | -0.030033 |
cmath.sin | 0.096490 |
boost.python経由でstd::sin | 0.005960 |
ctypesでlibmのsin | 0.771372 |
ctypesが若干遅いですね。CやC++のコードを1行も書かずにPythonだけでネイティブコードを呼び出せるのは大きなメリットですが、細かい粒度の関数を呼びたい場合はboost.pythonで書いたほうがよさそうな気がする。でも10us程度かかる処理以上のものであれば、ctypesでもいいんじゃないかと思います。これってかなり敷居が低いような気が…
サンプルコードは以下の通り。
boost.pythonからstd::sinを呼ぶもの。
#include <boost/python.hpp> #include <cmath> using namespace boost::python; double sin(double arg){ return std::sin(arg); } BOOST_PYTHON_MODULE(mymath){ def("sin", sin); } // g++ -O9 -I/usr/include/python2.5 -shared -o mymath.so mymath.C -lboost_python -fPIC
pythonの測定コード。これもけっこういい加減です。
#! /usr/bin/python import os,sys import random,time import ctypes import ctypes.util class bench: def __init__(self): random.seed() pass def bench(self, runs): st=time.time() x=self.bench_each(runs) en=time.time() d=en-st print "%d runs %f sec (%f iter/usec, %f usec/iter) %s" %(runs, d, runs/(d*1000000.0), d*1000000.0/runs, self) def bench_each(self, runs): for i in xrange(runs): self.myfunc(random.random()) def myfunc(self, arg): return arg class bench_mymath(bench): def __init__(self): import mymath bench.__init__(self) self.myfunc=mymath.sin class bench_cmath(bench): def __init__(self): import cmath bench.__init__(self) self.myfunc=cmath.sin class bench_math(bench): def __init__(self): import math bench.__init__(self) self.myfunc=math.sin class bench_ctypes(bench): def __init__(self, libname="m"): bench.__init__(self) name=ctypes.util.find_library(libname) self.mylib=ctypes.cdll.LoadLibrary(name) self.myfunc=self.mylib.sin self.myfunc.argtypes=[ctypes.c_double] self.myfunc.restype=ctypes.c_double bb=bench() bm=bench_math() bM=bench_cmath() bmy=bench_mymath() bc=bench_ctypes() N=2000000 for i in [bb,bm,bM,bmy,bc]: i.bench(N)
一応ファイルでも置いておきます。