8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

boost::python::numpyを使う、VSCodeでデバッグする

Last updated at Posted at 2020-08-26

c++でPythonを拡張したり、Numpyのndarrayをc++のvectorに変換したいというニーズがあると思います。
そういったときにBoost::pythonやboost::python::numpyは便利です。
(最近はpybind11というのも流行ってるみたいです。早く知りたかった...)

また、実はboost pythonは実行可能ファイルとしてビルドできるので、例えばVSCodeなどでデバッグできます。

必要なパッケージなどから1から解説していきます。

環境

備考
OS ubuntu 20.04 docker 上で起動
python 3.8.2
boost 1.73.0
cmake 3.16.3

準備

まず必要パッケージのインストールをします。

今回の記事で必要となるパッケージはwgetbuild-essentialcmakepython3-devpython3-pipでした。
自分の環境に合わせて適宜インストールしてください。

$ apt install wget build-essential cmake python3-dev python3-pip 

また以下のコマンドで Python のインストール位置とバージョンを確認しておきます。

$ which python3
/usr/bin/python3
$ python3 --version
Python 3.8.2

boost::python::numpyを使う場合はnumpyをインストールする必要があります。(私はこれを忘れていて苦労しました。)

$ pip3 install numpy

次にboostをダウンロード、インストールします。次の公式ドキュメントの5章を参考にインストールします。

Unix環境でのboostの始め方(英語)
boostのダウンロードページ

boostをダウンロードし、解凍し、解凍したフォルダの中に入ります。

$ wget (boostのダウンロードURL)
$ tar --bzip2 -xf boost_1_73_0.tar.bz2
$ cd boost_1_73_0

解凍したフォルダの中にある./bootstrap.shでインストール準備して、./b2でビルドとインストールをします。

$ ./bootstrap.sh --with-libraries=python --with-python=python3 --with-python-version=3.8
$ ./b2 install -j8

--with-libraries=pythonと指定することで、boost::pythonだけがビルド、インストールされ、時間が節約できます。
また--with-python-versionで先程確認したpythonのバージョンを指定します。

(Anacondaやpyenvなど別環境の場合は追加で別の設定が必要だと思われます。当記事はコンテナ中で構築してるのでいくらでも環境を汚してもいいかなというスタンスでやっています。)

デフォルトでは/usr/以下にboostがインストールされました。
findコマンドを使うことでboostのインクルードディレクトリとライブラリのパスがわかります。

$ find /usr/ -name "*boost*" | grep include
/usr/include/c++/9/bits/boost_concept_check.h
/usr/local/include/boost
/usr/local/include/boost/chrono/typeof/boost
...
$ find /usr/ -name "*boost*" | grep lib
...
/usr/local/lib/cmake/boost_numpy-1.73.0/libboost_numpy-variant-static-py3.8.cmake
/usr/local/lib/cmake/boost_numpy-1.73.0/boost_numpy-config-version.cmake
/usr/local/lib/cmake/boost_numpy-1.73.0/libboost_numpy-variant-shared-py3.8.cmake
/usr/local/lib/libboost_numpy38.a
/usr/local/lib/libboost_python38.so.1.73
/usr/local/lib/libboost_numpy38.so.1.73
...

実際にndarrayを使うモジュールを作ってみる

実際にndarrayの第1次元のサイズを返す関数を持つサンプルモジュールを作ってみます。

sample.cpp
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
//placeholdersの衝突を回避するため(要調査)
#include <boost/python/numpy.hpp>
namespace py = boost::python;
namespace np = boost::python::numpy;

auto size(np::ndarray &a)
{
  auto N = a.shape(0);
  return N;
}

BOOST_PYTHON_MODULE(sample)
{
  Py_Initialize();
  np::initialize();
  py::def("size", size);
}

これをg++でコンパイルするには、以下のコマンドを実行します。

g++ --shared -fPIC \
	-I$BOOST_INCLUDE_DIR -I/usr/include/python3.8/ \
	sample.cpp -Xlinker -rpath -Xlinker $BOOST_LIB_DIR \
	-lboost_python38 -lboost_numpy38 -o sample.so

$BOOST_INCLUDE_DIRはboostのインクルードパス、$BOOST_LIB_DIRはboostのライブラリパスで、環境によって違うと思います。私の環境の場合、さきほどfindコマンドを使って確かめたようにそれぞれ/usr/local/include/boost//usr/local/lib/でした。

環境によってコマンドを変えなければならないし、毎回このコマンドを打つのも大変ですのでCMakeを使います。

CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(boost-python-test)

find_package(Python3 COMPONENTS Development)
find_package(Boost COMPONENTS python38 numpy38 REQUIRED)
# ここでpython38となっているのはboostインストール時に--with-python-version=3.8としたからだと思われます。
# ここも環境によって変えなくてはならないかもしれません

# デバッグ用:解決したパスを表示
message("## Boost_LIBRARIES:  ${Boost_LIBRARIES}")  
message("## Boost_INCLUDE_DIRS: ${Boost_INCLUDE_DIRS}")
message("## Python3_INCLUDE_DIRS: ${Python3_INCLUDE_DIRS}") 
message("## Python3_LIBRARIES :${Python3_LIBRARIES}")

add_library(sample SHARED sample.cpp)
target_include_directories(sample PRIVATE ${Python3_INCLUDE_DIRS})
target_link_libraries(sample PRIVATE ${Boost_LIBRARIES} ${Python3_LIBRARIES})
# target_link_libraries(sample PRIVATE ${Boost_LIBRARIES})だけでも動く。理由不明。
set_target_properties(sample PROPERTIES PREFIX "") # 接頭辞'lib'を省略するため

(g++の使い方や、cmakeの使い方については

などが詳しいです。これを見ればコンパイルの仕組みから、上で使っているfind_packageの仕組みまで一通りわかります。感謝!)

そしてこのsample.cppCMakeLists.txtを以下のようなディレクトリ構成で配置します。

.
|-- CMakeLists.txt
|-- build
`-- sample.cpp

build内でビルドします。

$ cd build
$ cmake ..
$ cmake --build .

そうするbuild内にsample.soというファイル(共有ライブラリ)ができています。
そしてbuild内で以下のようなpythonスクリプトを書いて実行すると、この共有ライブラリをimportしてくれて、うまく動くことがわかります。

test.py
import sample
import numpy as np

a = np.array([1,2,3,4], dtype=np.float32)
print(a)
size=sample.size(a)
print(size)
$ python3 test.py 
[1. 2. 3. 4.]
4

VSCodeでデバッグする

pythonのモジュールとして呼び出している限り、デバックしづらくて使いにくいですが、
実行可能ファイルとしてビルドできれば、VSCodeのCMake-Toolsなどを使えばデバッグが捗ります。

Visual Studio CodeのRemote ContainersでC++開発環境構築
を参考に、VSCodeのリモートデバッグ環境を整えます。
(上記記事中の環境のリポジトリを私が改良したリポジトリもあります。)

まずその環境上で次のようなディレクトリ構成を作ります。

.
|-- CMakeLists.txt
|-- build
`-- main.cpp
CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(boost-python-test)

find_package(Python3 COMPONENTS Development)
find_package(Boost COMPONENTS python38 numpy38 REQUIRED)

add_executable(main_app main.cpp)
target_include_directories(main_app PRIVATE ${Python3_INCLUDE_DIRS})
target_link_libraries(main_app PRIVATE ${Boost_LIBRARIES} ${Python3_LIBRARIES})

main.cppで以下のように、main関数の先頭でPy_Initialize()np::initialize()を呼び出せば、実行可能ファイルとしてビルドできるようになります。

main.cpp
#include <iostream>
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
//placeholdersの衝突を回避するため(要調査)
#include <boost/python/numpy.hpp>
namespace py = boost::python;
namespace np = boost::python::numpy;

int main()
{
  Py_Initialize();
  np::initialize();
  double v[] = {1.3 , 2.4 , -5.3};
  int v_size = 3;
  py::tuple shape = py::make_tuple(v_size);
  py::tuple stride = py::make_tuple(sizeof(double));
  np::dtype dt = np::dtype::get_builtin<double>();
  np::ndarray output = np::from_data(&v[0], dt, shape, stride, py::object());
  np::ndarray output_array = output.copy();
  
  std::cout << "c++ array address::" << std::endl
          << std::hex << &v[0] << std::endl;
  
  auto *p = reinterpret_cast<double*>(output.get_data());
  std::cout << "ndarray address ::" << std::endl
          << std::hex << p << std::endl;
  return 0;
}

この状態で、VSCodeのCMake-Toolsのデバッグ機能を使えば、ブレークポイントを設定できたり、変数を覗けたりします。下の画像のようにVSCodeのDEBUG CONSOLEを使えば、まるでインタプリタ言語のようにC++が扱え、デバッグが捗ります。便利!

image.png

補足:過去の記事や参考サイト

Qiitaの過去記事や、外部サイトにBoost::pythonについての記事はありますが、
なかなか私が詰まった点の解説や、この記事で言われているようないわゆるモダンなcmakeの使い方をしている記事がなかったのでこの記事を作りました。

C++でPythonを拡張するためのBoost.NumPyチュートリアル(実践編)
この記事ではboost-numpyを使っていますが、boost-numpyはboost::pythonに統合され、今はdeprecatedとなってしまっています。

C++ で Python 用ライブラリーを作成する
PythonとC++間でのnumpy配列の受け渡し
これらの記事も大きく参考になりました。

8
1
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
8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?