pybind11は、numpyアレイとC++ Eigen線形代数ライブラリを含んだC++コードをpython用にラッピングするためのスマートな方法を提供している。cppimportと一緒に使うことで、C++とPythonを融合するための非常に便利なワークフローを提供してくれる。
! pip3 install pybind11
! pip3 install cppimport
Using pybind11¶
今回のチュートリアル用のディレクトリと各種ファイルを作成する。
%mkdir example1
cd example1
%%file funcs.hpp
int add(int i, int j);
%%file funcs.cpp
int add(int i, int j) {
return i + j;
};
次に、wrap.cppにpybind11を使ってC++ラッパーコードを書く。エクスポートされた関数定義の引数”i”_a=1, “j”_a=2は、pybind11に対しadd関数用に初期値1の変数iと初期値2の変数jを生成するように指示する。
%%file wrap1.cpp
#include <pybind11/pybind11.h>
#include "funcs.hpp"
namespace py = pybind11;
using namespace pybind11::literals;
PYBIND11_PLUGIN(wrap1) {
py::module m("wrap1", "pybind11 example plugin");
m.def("add", &add, "A function which adds two numbers",
"i"_a=1, "j"_a=2);
return m.ptr();
}
最後に、ほぼ定型コードである拡張モジュールをコンパイルするためのsetup.pyを書く。
%%file setup.py
import os, sys
from distutils.core import setup, Extension
from distutils import sysconfig
cpp_args = ['-std=c++11']
ext_modules = [
Extension(
'wrap1',
['funcs.cpp', 'wrap1.cpp'],
include_dirs=['pybind11/include'],
language='c++',
extra_compile_args = cpp_args,
),
]
setup(
name='wrap1',
version='0.0.1',
author='Cliburn Chan',
author_email='cliburn.chan@duke.edu',
description='Example',
ext_modules=ext_modules,
)
上で作成したファイルを用いて拡張モジュールをサブディレクトリにビルドする。
!python setup.py build_ext -i
pybind11.hの場所が分からないようなので教えてやる。
!find / -name pybind11.h
!python setup.py build_ext -i -I /root/pytorch/third_party/pybind11/include
エラーがなければfuncs.so拡張モジュールができたはずなので、その新しいモジュールをテストするためにtest_funcs.pyを書く。
%%file test_funcs.py
import wrap1
def test_add():
print(wrap1.add(3, 4))
assert(wrap1.add(3, 4) == 7)
if __name__ == '__main__':
test_add()
上で書いたテストを実行する。エラーメッセージは出ないはず。
!python test_funcs.py
Using cppimport¶
開発時に、拡張モジュールを再ビルドする度にpython setup.py clean &&
とやるのは面倒なので、cppimportにこの作業をやってもらう。
python setup.py build_ext -i
新しいサブディレクトリexaample2を作成して、example1ディレクトリからfunc.hpp, funcs.cpp, wrap.cppファイルをコピーする。前回の例に対しては、wrap.cppファイルのトップ(<%と %>の間)にいくつかの注釈を付け加えるだけで済む。
cd ..
%mkdir example2
%cp example1/funcs.* example2/
cd example2
ls
%%file wrap2.cpp
<%
cfg['compiler_args'] = ['-std=c++11']
cfg['sources'] = ['funcs.cpp']
setup_pybind11(cfg)
%>
#include "funcs.hpp"
#include <pybind11/pybind11.h>
namespace py = pybind11;
PYBIND11_PLUGIN(wrap2) {
py::module m("wrap2", "pybind11 example plugin");
m.def("add", &add, "A function which adds two numbers");
return m.ptr();
}
%%file test_funcs.py
import cppimport
funcs = cppimport.imp("wrap2")
def test_add():
assert(funcs.add(3, 4) == 7)
if __name__ == '__main__':
print(funcs.add(3,4))
test_add()
!python test_funcs.py
または、ノートプックからwrap2関数を直接コールしてもいい。
import cppimport
funcs = cppimport.imp("wrap2")
funcs.add(3, 4)
手動で拡張モジュールをビルド必要なしに、全てのアップデートはcppimportが検出して、自動的に再ビルドをトリガーしてくれる。
Vectorizing functions for use with numpy arrays¶
以下の例でsquare関数のベクトル化方法を示す。ここから、コードスニペット用のヘッダーと実装ファイルを個別に使う面倒は止めて、単にそれらをcode.cppファイルにラッピングコードと一緒に書いていることに留意する。つまり、cppimportを使えば、実際にコードするファイルは、C++用のcode.cppとPython用test fileの2ファイルだけで済むということ。
cd ..
%mkdir example3
%cd example3
%%file wrap3.cpp
<%
cfg['compiler_args'] = ['-std=c++11']
setup_pybind11(cfg)
%>
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
double square(double x) {
return x * x;
}
PYBIND11_PLUGIN(wrap3) {
py::module m("wrap3", "pybind11 example plugin");
m.def("square", py::vectorize(square), "A vectroized square function.");
return m.ptr();
}
import cppimport
wrap3 = cppimport.imp("wrap3")
wrap3.square([1,2,3])
一旦共用ライブラリがビルドされれば、標準Pythonモジュールとしてそれを使える。
! ls
import wrap3
wrap3.square([2,4,6])
Using numpy arrays as function arguments and return values¶
以下の例で関数内外へのnumpyアレイの渡し方を示す。これらのnumpyアレイ引数は、無印py:arrayか型付py:array_t
void *ptr;
size_t itemsize;
std::string format;
int ndim;
std::vector<size_t> shape;
std::vector<size_t> strides;
};
以下に2つの関数のC++コードを記す。関数twiceは、ポインターを使って渡されたnumpyアレイのin-placeでの変換方法を示し、関数sumは、numpyアレイの要素の足し方を示す。buffer_infoの情報を利用して、コードは任意のn-dアレイに対して機能する。
cd ..
%mkdir example4
cd example4
%%file wrap4.cpp
<%
cfg['compiler_args'] = ['-std=c++11']
setup_pybind11(cfg)
%>
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
// Passing in an array of doubles
void twice(py::array_t<double> xs) {
py::buffer_info info = xs.request();
auto ptr = static_cast<double *>(info.ptr);
int n = 1;
for (auto r: info.shape) {
n *= r;
}
for (int i = 0; i <n; i++) {
*ptr++ *= 2;
}
}
// Passing in a generic array
double sum(py::array xs) {
py::buffer_info info = xs.request();
auto ptr = static_cast<double *>(info.ptr);
int n = 1;
for (auto r: info.shape) {
n *= r;
}
double s = 0.0;
for (int i = 0; i <n; i++) {
s += *ptr++;
}
return s;
}
PYBIND11_PLUGIN(wrap4) {
pybind11::module m("wrap4", "auto-compiled c++ extension");
m.def("sum", &sum);
m.def("twice", &twice);
return m.ptr();
}
%%file test_code.py
import cppimport
import numpy as np
code = cppimport.imp("wrap4")
if __name__ == '__main__':
xs = np.arange(12).reshape(3,4).astype('float')
print(xs)
print("np :", xs.sum())
print("cpp:", code.sum(xs))
print()
code.twice(xs)
print(xs)
!python test_code.py
ls
import wrap4 as wp
import numpy as np
xs = np.arange(12).reshape(3,4).astype('float')
print(xs)
print("np :", wp.sum(xs))
print()
wp.twice(xs)
print(xs)