【pybind11】 C++コードをPythonで呼び出して実行する方法

なぜPythonでC++コードを呼び出したいのか...それは高速化のためです.

そもそもPythonの実行速度は非常に遅く,C++と比べて経験的に10倍から100倍程度のパフォーマンスの違いがあります.

この記事では,PythonからC++を呼び出して利用するためのライブラリである「pybind11」の使い方を記載します.

目次

pybind11とは

PythonでC++を利用するためのライブラリです.似たようなライブラリとして,Boost.Pythonなどもありますが,Boostよりも比較的簡単にインストールできるため,利用しやすいものです.簡単にいえば,余計なものを除いた軽量のBoost.Pythonのようです.(引用より)

Think of this library as a tiny self-contained version of Boost.Python with everything stripped away that isn’t relevant for binding generation.

https://github.com/pybind/pybind11

pybind11のインストール

venv環境

まずは,仮想環境として,venvを利用します.仮想環境が不要であれば,この手順はスキップできます.


python -m venv my_venv
source my_venv/bin/activate

pybind11のインストール

pipでpybind11をインストールします.


pip install pybind11

システムにインストールしたい場合は,下記コマンドになります.


pip install pybind11[global]

使い方

公式ドキュメントの例題を利用して,使い方を示します.

PythonでC++を呼び出すための手順は下記のようになります.

  1. C++で呼び出したい関数やクラスを作成し,バインディングコードを作成
  2. pybind11を利用し,C++コードをビルド
  3. Pythonでimportして実行

C++のコードを記述

下記のようなコードを作成します.引数を加算して返り値とする簡単な関数です.

example.cpp


#include <pybind11/pybind11.h>

int add(int i, int j) {
    return i + j;
}

PYBIND11_MODULE(example, m) {
    m.doc() = "pybind11 example plugin"; // optional module docstring
    m.def("add", &add, "A function that adds two numbers");
}

1行目と7-10行目がpybind11のバインディングコードです.

次に下記のように,ビルドします.


g++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix)

ビルドに成功すると,example.cpython-311-x86_64-linux-gnu.soという名前の共有ライブラリが作成されます.

C++ライブラリが作成できたら,続いてPythonコードを下記のように記述します.

example.py


import example

def main():
    print(example.add(1,2))


if __name__ == "__main__":
    main()

1行目で作成したライブラリをインポートします.インポートする名前はexample.cppの7行目で指定した名前(example)になります.

Pythonを実行すると下記のような結果が得られます.結果は3=1+2です.


python example.py 
3

クラスを使用する場合

上記では,C++関数をPythonで使用する方法を示しました.ただ,実用上は関数ではなくクラスを用いることが大半です.

ここからはC++クラスをPythonで呼び出す方法を示します.

下記のように,クラスを含むC++コードを用意します.

my_class.cpp


#include <pybind11/pybind11.h>

#include <string>

class Animal {
public:
  virtual ~Animal() {}
  virtual std::string go(int n_times) = 0;
};

class Dog : public Animal {
public:
  std::string go(int n_times) override {
    std::string result;
    for (int i = 0; i < n_times; ++i)
      result += "woof! ";
    return result;
  }
};

std::string call_go(Animal *animal) { return animal->go(3); }

PYBIND11_MODULE(my_class, m) {
  pybind11::class_<Animal>(m, "Animal").def("go", &Animal::go);
  pybind11::class_<Dog, Animal>(m, "Dog").def(pybind11::init<>());
  m.def("call_go", &call_go);
}

ビルドは,関数の場合もクラスの場合も全く同じです.


g++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) my_class.cpp -o my_class$(python3-config --extension-suffix)

同様にPythonでimportし,クラスを呼び出します.


import my_class

def main():
    animal = my_class.Dog()
    print(my_class.call_go(animal))


if __name__ == "__main__":
    main()

python my_class.py 
woof! woof! woof! 

C++のクラスを継承するなど,ある程度複雑なこともできるらしいですが,今回はそこまで扱いません(というかC++で書けばよいと思います).詳しくは下記URL.

https://pybind11.readthedocs.io/en/stable/advanced/classes.html

まとめ

PythonからC++を呼び出すためのライブラリとしてpybind11の使い方を紹介しました.

PythonからC++に変更するだけで十分に高速ですが,並列化などによりさらなる高速化することが可能です.

よかったらシェアしてね!
目次