LoginSignup
3
2

More than 5 years have passed since last update.

pybind11を使ってみる

Last updated at Posted at 2018-11-22

はじめに

高速化や昔のプログラムの流用などで、PythonからのC++呼び出したくなることがある。
前々から興味があったpybind11を試してみる。
この記事では公式PDF(2018.11.20時点)の4章を説明する。

環境

MacMacBook Pro (13-inch, Late 2016)
MacOS 10.12.6 Sierra

Python
pyenv 1.2.8
python 3.6.4

C++
clang-900.0.39.2

作業1: (4.3 Creating bindings for a simple function)

> pip install pybind11

でinstallできる。お手軽。
2018年11月20日の時点でinstallされるのはv2.2.4

とりあえず、説明のPDFの4章から見てみる。

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 which adds two numbers");
}

を作って、

> clang++ -O3 -Wall -shared -std=c++11 -fPIC `python -m pybind11 --includes` -undefined dynamic_lookup example.cpp -o example`python3-config --extension-suffix`

そうすると、example.cpython-36m-darwin.soみたいなファイルが出来上がる。
なお、Macでやる時だけかもしれないけれど、-undefined dynamic_lookupのオプションをつけないとerrorが出るので注意。

さて、あとはPythonで

>>> import example
>>> example.__doc__
'pybind11 example plugin'
>>> print(example.add.__doc__)
add(arg0: int, arg1: int) -> int

A function which adds two numbers
>>> example.add(1, 2)
3
>>> example.add(45, 23)
68

のようになる。

作業2: 脱線 速度計測

簡単な例で比較してみる。まずはpython側の重い和

test_py.py
from timeit import timeit

def sum_up(n):
    sum_ = 0
    for i in range(n):
        sum_ += i
    return sum_

if __name__ == '__main__':
    n_itr = 100
    n_sum = 10_000
    ave_ = timeit(lambda: sum_up(n_sum), number=n_itr)/n_itr
    print(f"{n_itr} loops, the average time is {ave_ * 1_000} ms.")
> python test_py.py
100 loops, the average time is 0.5563870701007545 ms.

次にC++側

example2.cpp
#include <pybind11/pybind11.h>

int sum(int n) {
  int sum_ = 0;
  for(int i = 0; i < n; ++i){
    sum_ += i;
  }
  return sum_;
}

PYBIND11_MODULE(example2, m) {
  m.doc() = "pybind11 example plugin"; // optional module docstring
  m.def("sum", &sum, "A function which calcurates the sum of the sequence.");
}
test_cpp.py
from timeit import timeit
import example2

if __name__ == '__main__':
    n_itr = 100
    n_sum = 10_000
    ave_ = timeit(lambda: example2.sum(n_sum), number=n_itr)/n_itr
    print(f"{n_itr} loops, the average time is {ave_ * 1_000} ms.")
> python test_cpp.py
100 loops, the average time is 0.00564772984944284 ms.

556 µs vs 5.65 µsでだいたい100倍ぐらい?
ものすごく簡単な実装だし、for loopっていうpython不利な形でこれぐらいの差。

作業3:(4.4 Keyword arguments)

keyword argumentsを実装してみる。
作業1のexample.cppの実装では、

>>> import example
>>> example.add(i=2, j=3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: add(): incompatible function arguments. The following argument types are supported:
    1. (arg0: int, arg1: int) -> int

Invoked with: kwargs: i=2, j=3

となってkeyword argsを受け付けてくれない。
pythonの関数は普通受け付けてくれるので、これをできるようにする。
次のようにexample.cppを書き直す。

example.cpp
#include <pybind11/pybind11.h>
namespace py = pybind11;

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

PYBIND11_MODULE(example, m) {
        m.doc() = "pybind11 example plugin";
        m.def("add", &add, "A function which adds two numbers",
              py::arg("i"), py::arg("j"));
}

すると、

>>> import example
>>> example.add(i=2, j=3)
5

と受け付けるようになる。引数がどっちがどっちかわからなくなるので、引き算のsub関数も追加する。

example.cpp
#include <pybind11/pybind11.h>
namespace py = pybind11;

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

int sub(int i, int j) {
        return i - j;
}

PYBIND11_MODULE(example, m) {
        m.doc() = "pybind11 example plugin";
        m.def("add", &add, "A function which adds two numbers",
              py::arg("i"), py::arg("j"));
        m.def("sub", &sub, "A function which subtracts the second arg from the first arg",
              py::arg("i"), py::arg("j"));
}
>>> import example
>>> example.sub(5, 2)
3
>>> example.sub(i=3, j=2)
1
>>> example.sub(j=3, i=2)  # jを先に入力した。
-1

ちなみに引数の名前はi, jにする必要はなく、

example.cpp
#include <pybind11/pybind11.h>
namespace py = pybind11;

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

int sub(int i, int j) {
        return i - j;
}

PYBIND11_MODULE(example, m) {
        m.doc() = "pybind11 example plugin";
        m.def("add", &add, "A function which adds two numbers",
              py::arg("i"), py::arg("j"));
        m.def("sub", &sub, "A function which subtracts the second arg from the first arg",
              py::arg("minuend"), py::arg("subtrahend"));
}

とすれば、

>>> import example
>>> example.sub(minuend=5, subtrahend=1)
4
>>> example.sub(subtrahend=1, minuend=19)
18

とできる。
なお、

example.cpp
#include <pybind11/pybind11.h>
namespace py = pybind11;

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

int sub(int i, int j) {
        return i - j;
}

PYBIND11_MODULE(example, m) {
        m.doc() = "pybind11 example plugin";
        m.def("add", &add, "A function which adds two numbers",
              py::arg("i"), py::arg("j"));
        m.def("sub", &sub, "A function which subtracts the second arg from the first arg",
              py::arg("minuend"), py::arg("subtrahend"));
        using namespace pybind11::literals;
        m.def("sub2", &sub, "A function which subtracts the second arg from the first arg",
              "i"_a, "j"_a);
}

example.sub2のように簡単な記法でkeyword argsを定義することもできる。

>>> import example
>>> example.sub2(3, 2)
1
>>> example.sub2(i=6, j=2)
4

作業4:(4.5 Default arguments)

default argumentsを実装してみる。
pythonの関数定義ではkeyword argsにdefault valueを設定することがよくある。
例えば

def func(i = 1, j = 2):
    return (i + 2) * j

func()
# ==> 6
func(1, 3)
# ==> 9
func(3)  # i = 3, j = 2
# ==> 10
func(j=3)
# ==> 9

という感じになる。これを。pybind11を用いて表そうとすると、次のようになる。

example.cpp
#include <pybind11/pybind11.h>
namespace py = pybind11;
using namespace pybind11::literals;

int func(int i, int j){
  return (i + 2) * j;
}

PYBIND11_MODULE(example, m) {
  m.doc() = "pybind11 example plugin";
  // regular notation
  m.def("func1", &func, "A function",
        py::arg("i") = 1, py::arg("j") = 2);
  // shorthand
  m.def("func2", &func, "A function",
        "i"_a = 1, "j"_a = 2);
}

arg("i") = 3のように defの中に記述すれば良い。

作業5 (4.6 Exporting variables)

pythonのmoduleには定数なのが設定されているものがある。それらは、attr 関数を使うと表現することができる。

example.cpp
#include <pybind11/pybind11.h>
namespace py = pybind11;

PYBIND11_MODULE(example, m) {
  m.doc() = "pybind11 example plugin";
  // integer
  m.attr("val1") = 34;
  // float/double
  m.attr("val2") = 3.4;
  // str
  m.attr("val3") = "This is 340.";
  // str
  py::object val4 = py::cast("This is another 340.");
  m.attr("val4") = val4;
}

これをコンパイルするとpythonの中で次のように呼べる。

>>> import example
>>> example.val1
34
>>> example.val2
3.4
>>> example.val3
'This is 340.'
>>> example.val4
'This is another 340.'

TODO

  • 別の記事でC++側のobjectをbindする方法やnumpyやEigenをbindする方法などがPDFに書かれていたのでそれをやる。

参考資料

公式ページ(link)
公式PDF(link)
[python高速化]pybind11によるc++組み込み

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2