pythonをC++化すると10倍早くなる と言われています。全てのpythonコードをC++する手もあります(割と大変)が、実際のボトルネックはプログラムの一部分であることが多いです。よって、pythonの部分的なC++化は、「C++化の実装コスト」と「プログラム高速化」を両立した優れたアプローチだと思います。
とはいえ、なんだかめんどくさそうですよね。いくつかの手段がありますが、本記事ではC++の呼び出しを最も簡単に実現できる(と思っている) pybind11
を紹介します。また、C++のコードでは、STLコンテナの std::vector
を使用します。
目次
1. pybind11のダウンロード
pybind11
は、軽いヘッダーのみのライブラリで、既存のC++コードのpythonバインディングを作成し、pythonへ公開します。
pybind11
を使用するためには、下記のどちらかの方法でソースコードを入手する必要があります:
pip
やconda
でインストールgithub
から ソースコードをダウンロード
ソースコードは頻繁にアップデートされているようなので、最新版を取得できるように github
からのダウンロードをオススメします。
ターミナルで以下のコマンドでダウンロードできます:
git clone https://github.com/pybind/pybind11.git
2. C++コード
例として、pythonの list
型をC++の std::vector
型として渡して、その中身を表示してみます。C++側は3つのファイルを用意します。
disp_vector.h
disp_vector.cpp
pymodulue.cpp
まずは、disp_vector.h
と disp_vector.cpp
を中身を載せます。
// disp_vector.h #ifndef __DISP_VECTOR__ #define __DISP_VECTOR__ #include <vector> using std::vector; double dispVector( const vector<int>& vec ); #endif
// disp_vector.cpp #include "disp_vector.h" #include <iostream> using std::cout; using std::endl; double dispVector( const vector<int>& vec ) { for ( const auto& v : vec ) cout << v << endl; return 0; }
これは、pybind11
に依存してなくて、C++のコードからでも関数 dispVector()
を呼び出すことが可能です。
次に、pythonから呼び出すための pymodulue.cpp
を説明します。
// pymodulue.cpp #include <pybind11/pybind11.h> #include <pybind11/stl.h> #include "disp_vector.h" PYBIND11_MODULE( mymodule, m ) { m.doc() = "mymodule"; m.def( "disp_vec", &dispVector, "display vector" ); }
pybind11
を使用するために、pybind11/pybind11.h
をインクルード- STLコンテナを使うために、
pybind11/stl.h
をインクルード - 呼び出したい関数が宣言されているヘッダファイル(
disp_vector.h
)をインクルード mymodule
の部分は、python側でimportするときのモジュール名になるm.doc()
は、モジュールのドキュメントm.def()
で関数を登録m.def( "python側で使用する関数名", c++側の関数名, "関数のドキュメント")
「C++でやりたいことをやるコード」と「それをpython側で使用するためのコード」を独立して書くことができるので、使い勝手がいいですね。
3. CMakeでコンパイル
C++のコードのコンパイルするには、マニュアルでする方法と CMake
でする方法があります。マニュアルの方は、g++
コマンド等で直接コンパイルする訳ですが、コンパイルオプションがかなりめんどうです。簡単にコンパイルできる CMake
を断然オススメします。この記事では、CMake
でコンパイルする方法を紹介します。
CMake
使ったこと無いからちょっと、、という方もいるかと思います。私もそうでした(笑)pybind11
をきっかけに CMake
使ってみたら、意外と簡単だったので、きっと大丈夫です。
まずは、CMake
が利用できる環境を準備します。
(MacOSの場合)
ターミナルで、
which cmake
と入力し、パスが出力されたら、CMake
はインストール済みなので、準備OKです。
もし、cmake not found
的なメッセージが出てきたら、Homebrew
でインストールしてください。
brew install cmake
(Linuxの場合)
pybind11
のドキュメントによると、python-dev
か python3-dev
が必要のようです。3以上でも問題無いと思うので、例えばUbuntuなら、以下のようなコマンドでインストールできます:
sudo apt install python3.7-dev
次に、コンパイル方法を説明していく訳ですが、その前にディレクトリ構成を載せます。
pybind11_test/ ├ pybind11/ ├ CMakeLists.txt ├ disp_vector.h ├ disp_vector.cpp └ pymodulue.cpp
pybind11
は、github
からクローンしたディレクトリです(シンボリックリンクでもOK)。
CMakeLists.txt
は、cmake
コマンドを利用するために必要なファイルです。中身は以下の通りです:
cmake_minimum_required(VERSION 3.2) project(test) set(PYBIND11_CPP_STANDARD -std=c++11) set(CMAKE_POSITION_INDEPENDENT_CODE ON) add_subdirectory(pybind11) add_library(disp_vector disp_vector.cpp) pybind11_add_module(mymodule pymodule.cpp) target_link_libraries(mymodule PRIVATE disp_vector)
cmake_minimum_required(VERSION 3.2)
で、CMake
のバージョン指定project(test)
は、プロジェクト名を定義していますが、今回は重要でないので、何でもOKset(PYBIND11_CPP_STANDARD -std=c++11)
で、C++のバージョンを指定add_subdirectory(pybind11)
で、インクルードするディレクトリを指定add_library(disp_vector disp_vector.cpp)
で、disp_vector.cpp
をコンパイルし、静的ライブラリlibdisp_vector.a
を生成pybind11_add_module(mymodule pymodule.cpp)
で、mymodule
というモジュール名のモジュールを生成target_link_libraries(mymodule PRIVATE disp_vector)
で、mymodule
に対してlibdisp_vector.a
をリンク
実際には、以下のようにしてビルドします:
(export CXX=path/to/g++)
mkdir build
cd build
cmake ..
make
OSのデフォルトのコンパイラによっては、export
が必要です。make
してエラーや警告が出た場合は、一度 rm -rf build
と build
ディレクトリを削除してやり直してください。削除せずに、export
してmake
してもうまくいきません。
無事ビルド完了すると、build
ディレクトリの中に mymodule.xxxxxx.so
みたいな名前のライブラリが生成されているはずです。
4. pythonから呼び出す
生成された mymodule.xxxxxx.so
をコピーして、mymodule
を呼び出すpythonコード(main.py
)と同じ場所に置きます。
import mymodule v = [ 3, 2, 5 ] mymodule.disp_vec( v ) print("document-------") print(mymodule.__doc__) print(mymodule.disp_vec.__doc__)
実行方法
python3 main.py
実行結果
3 2 5 document------- mymodule disp_vec(arg0: List[int]) -> float display vector
少し細かく説明したので長くなりましたが、一度仕組みを理解すれば次回からは簡単にC++化できそうです。
5. 利用可能なSTLコンテナ一覧
本記事では、std::vector
を紹介しました。他にも、pybind11
では様々なSTLコンテナを利用可能です。対応するpythonの型と、必要なヘッダファイルと合わせてまとめます。
STLコンテナ | pythonの型 | ヘッダファイル |
---|---|---|
vector |
list |
pybind11/stl.h |
deque |
list |
pybind11/stl.h |
list |
list |
pybind11/stl.h |
array |
list |
pybind11/stl.h |
set |
set |
pybind11/stl.h |
unordered_set |
set |
pybind11/stl.h |
map |
dict |
pybind11/stl.h |
unordered_map |
dict |
pybind11/stl.h |
pair |
tuple |
pybind11/pybind11.h |
tuple |
tuple |
pybind11/pybind11.h |