数学やプログラミングの備忘録

数理最適化, Python, C++をメインに紹介するブログ。

MENU

pythonでC++(std::vector)を呼び出す

pythonC++化すると10倍早くなる と言われています。全てのpythonコードをC++する手もあります(割と大変)が、実際のボトルネックはプログラムの一部分であることが多いです。よって、pythonの部分的なC++化は、「C++化の実装コスト」と「プログラム高速化」を両立した優れたアプローチだと思います

とはいえ、なんだかめんどくさそうですよね。いくつかの手段がありますが、本記事ではC++の呼び出しを最も簡単に実現できる(と思っている) pybind11 を紹介します。また、C++のコードでは、STLコンテナの std::vector を使用します。

目次

  1. pybind11のダウンロード
  2. C++コード
  3. CMakeでコンパイル
  4. pythonから呼び出す
  5. 利用可能なSTLコンテナ一覧

1. pybind11のダウンロード

pybind11は、軽いヘッダーのみのライブラリで、既存のC++コードのpythonバインディングを作成し、pythonへ公開します。 pybind11を使用するためには、下記のどちらかの方法でソースコードを入手する必要があります:

  • pipconda でインストール
  • github から ソースコードをダウンロード

ソースコードは頻繁にアップデートされているようなので、最新版を取得できるように github からのダウンロードをオススメします。

ターミナルで以下のコマンドでダウンロードできます:

git clone https://github.com/pybind/pybind11.git

2. C++コード

例として、pythonlist 型をC++std::vector 型として渡して、その中身を表示してみます。C++側は3つのファイルを用意します。

  • disp_vector.h
  • disp_vector.cpp
  • pymodulue.cpp

まずは、disp_vector.hdisp_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-devpython3-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) は、プロジェクト名を定義していますが、今回は重要でないので、何でもOK
  • set(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 をリンク

実際には、以下のようにしてビルドします:

  1. (export CXX=path/to/g++)
  2. mkdir build
  3. cd build
  4. cmake ..
  5. make

OSのデフォルトのコンパイラによっては、export が必要です。make してエラーや警告が出た場合は、一度 rm -rf buildbuild ディレクトリを削除してやり直してください。削除せずに、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