NumPy や SciPy をはじめとする科学計算ライブラリは機能が豊富なだけでなく、インタプリタ言語とは思えないほど非常に高速に動作します。それもそのはずで、ソースコードを読めばわかるとおり、内部の実装においては C や Fortran といったコンパイル言語で書かれた枯れた機能を利用しています。
これらにならい Python や Ruby などで定期的に何度も呼び出す計算のうち、汎用性のあるものについては C/C++ といったコンパイル言語を利用して拡張機能として記述すると効率的になります。メリットとデメリットは次の通りです。
- メリット … その部分の処理が数十倍以上と高速になる
- デメリット … C/C++ の実装に依存し可搬性を失う、実装が複雑になる
プロトタイピングの一環としてあとで C/C++ で実装する前にインタプリタ言語を利用する場合においても、ライブラリの部分を C/C++ で記述しておけば再利用が可能になる場合もあります。
Python の拡張ライブラリを C++ で書く
詳細な説明は公式のドキュメントを読むのが良いでしょう。
C や C++ による Python の拡張
http://docs.python.jp/3.3/extending/extending.html
Python API は "Python.h" を組み込むことで C の世界に取り込むことができます。これらは C/C++ 標準ライブラリの前に呼ばなくてはなりません。
引数
static PyObject *
mymethod(PyObject *self, PyObject *args) {
const char *text;
if (!PyArg_ParseTuple(args, "s", &text))
return NULL;
...
}
関数は常に 2 つの引数を持ち、習慣的に self および args として扱うのが一般的です。
メソッドテーブル
Python からどのようにオブジェクトにアクセスしメソッドを呼ぶのか、これを定義するのが PyMethodDef によるメソッドテーブルです。
static PyMethodDef SomeMethods[] = {
...
{"module_name", module_name, METH_VARARGS,
"説明をここに書く"},
...
{NULL, NULL, 0, NULL} /* Sentinel */
};
ライブラリの登録
ライブラリが最初に呼ばれた時に初期化するコードが PyMODINIT_FUNC です。 PyModule_Create() が返るようにします。
PyMODINIT_FUNC
PyInit_module_name(void) {
return PyModule_Create(&module_name);
}
拡張モジュールのビルド
distutils でビルドするのが良いでしょう。
from distutils.core import setup, Extension
module = Extension('module_name', ['mymodule.cpp'])
setup(name='module_name',
version='1.0',
ext_modules=[module],
)
あとは setup.py を利用すれば使うことができます。
python setup.py build
python setup.py install # システムにインストールする場合
インストールするといつでも import できるようになります。しない場合 .so ファイルのパスを指定して import すれば良いです。
import module_name
module_name.some_method('args')
C++ を利用する
正式な C++ の規格については規格書を読むのが良いでしょう。あるいは C++ の参考書が公開されているのでそちらを参照するのも良いです。
Boost を利用する
Boost は C++ 標準化委員会の委員らをはじめとする手による自由なライブラリで、テンプレートを活用したモダンなプログラミングをすることができます。次の例は boost::split を利用して文字列を分割する例です。
#include <boost/algorithm/string.hpp> // Boost を使う
#include <boost/foreach.hpp>
#include <string>
#include <list>
#include <iostream>
using namespace std; // std:: を名前空間に持ち込む
int main ()
{
string str ("192.168.0.1 192.168.0.2");
list<string> list_string;
boost::split(list_string, str, boost::is_space()); // スペースで文字列を分割
BOOST_FOREACH(string s, list_string) {
cout << s << endl;
}
return 0;
}
C++11 を利用する
執筆段階では C++11 のマイナーアップデートである C++14 がすでにドラフトで議論されていますが、 2014 年ということを考えると少なくとも C++11 以降に準拠したコードを書くのが良いでしょう。
以下は C++11 の std::array を利用する例です。既定の配列は std::vector に比べて機能が少ないです。これに対し std::array は生配列をラップして std::vector のように使えるようにします。ラッパーなので元の配列と比較しても内部のコストや速度がほぼ変わらないという特徴があります。
#include <algorithm>
#include <functional>
#include <array> // std::array
#include <iostream>
int main()
{
std::array<int, 10> s = {5, 7, 4, 2, 8, 6, 1, 9, 0, 3}; // std::array による配列
std::sort(s.begin(), s.end(), std::greater<int>()); // 配列のソートに std::sort を利用する
for (int a : s) {
std::cout << a << " "; // ソートして標準出力に書き出す
}
std::cout << '\n';
}
C++ を使うための細かいテクニック
zsh の alias -s は拡張子に対して関数を割り当てることができ、これを利用するとあたかもスクリプト言語のように C++ を使えるようになります。
function runcpp () { g++ -std=c++11 $1 && shift && ./a.out $@ } # -std=c++11 で C++11 準拠とする
alias -s {c,cpp}=runcpp # 拡張子に対して関数を割り当てる
あとは ./my.cpp のようにあたかも実行属性が付与されているかのごとくソースコードを起動すれば処理結果が返ります。
C++11 に準拠するのは g++ の 4.7 以降ですから注意しましょう。 CentOS 6 などパッケージの古いディストリビューションではそのまま使えないことがあります。 (CentOS 6 の場合、リポジトリの追加で利用可能になります)
まとめ
グルー言語として名高い Python ですが、肝心な部分を C++ による拡張ライブラリで書くなどすると、ボトルネックを高速化させることもできるようになります。たとえば統計・機械学習ライブラリを新たに実装するにあたり、ネイティブで書くより拡張ライブラリにしたほうが現実的な速度を考えて適しているといったケースが考えられます。より応用の幅が広がるでしょう。