TadaoYamaokaの日記

山岡忠夫Homeで公開しているプログラムの開発ネタを中心に書いていきます。

UbuntuでBoostをビルドしてBoost.PythonとBoost.Numpyを使う

Ubuntu 16.04 LTSのaptでインストールできるBoostのバージョンは1.58なのでBoost.Numpyが使えない。
そこで、最新バージョンのBoostをソースからビルドした。

手順は、
Boost Getting Started on Unix Variants - 1.66.0
を参照した。

ソースダウンロード

wget https://dl.bintray.com/boostorg/release/1.66.0/source/boost_1_66_0.tar.bz2

解凍

tar --bzip2 -xf boost_1_66_0.tar.bz2

Pythonインストール

Boostをビルドする前にPythonをインストールする。
pyenvでAnaconda3 5.0.1をインストールしてデフォルトに設定しておく。
参考:Ubuntu 16.04 LTSにChainerをインストールする - TadaoYamaokaの日記

$ git clone git://github.com/yyuu/pyenv.git ~/.pyenv
$ echo 'export PYENV_ROOT="${HOME}/.pyenv"' >> ~/.bashrc
$ echo 'export PATH="${PYENV_ROOT}/bin:$PATH"' >> ~/.bashrc
$ echo 'eval "$(pyenv init -)"' >> ~/.bashrc

再ログイン

$ pyenv install --list
$ pyenv install anaconda3-5.0.1
$ pyenv versions
$ pyenv global anaconda3-5.0.1
$ pyenv versions

Ubuntu 16.4 LTSのgccより、Anacondaがコンパイルされたgccが古いとリンク時にpython3のライブラリの競合が起きる。
Anaconda 4.2.0のpython3を使うとリンク時に以下のエラーが発生した。

g++ -std=c++11 -I/home/xxx/.pyenv/versions/anaconda3-4.2.0/include/python3.5m -o obj/a.o -c a.cpp
g++ -o bin/a obj/a.o -L/home/xxx/.pyenv/versions/anaconda3-4.2.0/lib -lboost_python3 -lboost_numpy3 -lpython3.5m -std=c++11
//usr/local/lib/libboost_python3.so: `std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_create(unsigned long&, unsigned long)@GLIBCXX_3.4.21' に対する定義されていない参照です
//usr/local/lib/libboost_python3.so: `std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_append(char const*, unsigned long)@GLIBCXX_3.4.21' に対する定義されていない参照です
//usr/local/lib/libboost_python3.so: `std::runtime_error::runtime_error(std::runtime_error const&)@GLIBCXX_3.4.21' に対する定義 されていない参照です
//usr/local/lib/libboost_python3.so: `std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct(unsigned long, char)@GLIBCXX_3.4.21' に対する定義されていない参照です
//usr/local/lib/libboost_python3.so: `std::range_error::range_error(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@GLIBCXX_3.4.21' に対する定義されていない参照です
//usr/local/lib/libboost_python3.so: `std::runtime_error::runtime_error(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@GLIBCXX_3.4.21' に対する定義されていない参照です
//usr/local/lib/libboost_python3.so: `std::__cxx11::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> >::_M_construct(unsigned long, wchar_t)@GLIBCXX_3.4.21' に対する定義されていない参照です
//usr/local/lib/libboost_python3.so: `std::__cxx11::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> >::_M_create(unsigned long&, unsigned long)@GLIBCXX_3.4.21' に対する定義されていない参照です
//usr/local/lib/libboost_python3.so: `std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::compare(char const*) const@GLIBCXX_3.4.21' に対する定義されていない参照です
collect2: error: ld returned 1 exit status
Makefile:12: ターゲット 'bin/a' のレシピで失敗しました
make: *** [bin/a] エラー 1

最新のAnaconda3 5.0.1を使用するとコンパイルされたgccのバージョンはUtuntuより新しいが、エラーなしでリンクできた。

Anaconda3 4.2.0のgccのバージョン
$ python
Python 3.5.2 |Anaconda 4.2.0 (64-bit)| (default, Jul  2 2016, 17:53:06)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
Anaconda3 5.0.1のgccのバージョン
$ python
Python 3.6.3 |Anaconda, Inc.| (default, Oct 13 2017, 12:02:49)
[GCC 7.2.0] on linux
Ubuntu 16.4 LTSのgccのバージョン
$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.6) 5.4.0 20160609

Boost.Pythonを有効化

ホームディレクトリに「user-config.jam」を作成し、以下のように編集する。

using python : : /home/xxx/.pyenv/versions/anaconda3-5.0.1/bin/python : /home/xxx/.pyenv/versions/anaconda3-5.0.1/include/python3.6m : /home/xxx/.pyenv/versions/anaconda3-5.0.1/lib ;

:で区切られた4つ目はインクルードディレクトリを指定し、5つ目はライブラリディレクトリを指定している。
なお、user-config.jam内では環境変数は使えない。

参考:Configuring Boost.Build - 1.66.0

ビルド

cd boost_1_66_0/
./bootstrap.sh
sudo ./b2 install

/usr/local/include/boost/
/usr/local/lib/
にヘッダーとライブラリがインストールされる。

ビルドをやり直すには、

sudo ./b2 clean

を実行してから、やり直す。

Boost.PythonとBoost.Numpyを使用したソース作成

以下ようなソースを作成する。
a/a.pyで作成したNumpyのndarrayをa.cpp側で取得して、ndarrayから値を取り出して表示する。

a.cpp
#include <iostream>
#include <boost/python/numpy.hpp>

using namespace std;

namespace py = boost::python;
namespace np = boost::python::numpy;

void call_py_a()
{
	// Boost.PythonとBoost.Numpyの初期化
	Py_Initialize();
	np::initialize();

	// Pythonモジュール読み込み
	py::object a_ns = py::import("a.a").attr("__dict__");

	// 関数取得
	py::object py_a = a_ns["a"];
	auto ret = py_a();
	np::ndarray data = py::extract<np::ndarray>(ret);
	float *val = reinterpret_cast<float*>(data.get_data());
	cout << val[0] << endl;
}

int main()
{
	call_py_a();

	return 0;
}
a/a.py
import numpy as np
def a():
    return np.array([123], dtype=np.float32)
setup.py
import setuptools

setuptools.setup(
    name = 'a',
    version = '0.0.1',
    author = 'a',
    packages = ['a'],
    scripts = [],
)

Pythonモジュールとしてインストールしておく。

pip install -e . --user

コンパイル

以下のようなMakefileを作成する。

Makefile
CC = g++
CFLAGS = -std=c++11
LDFLAGS = -lboost_python3 -lboost_numpy3 -lpython3.6m
INCLUDE = -I $(PYENV_ROOT)/versions/anaconda3-5.0.1/include/python3.6m
LIB = -L$(PYENV_ROOT)/versions/anaconda3-5.0.1/lib

target = bin/a
sources = a.cpp
objects = $(addprefix obj/, $(sources:.cpp=.o))

$(target): $(objects)
	@[ -d bin ] || mkdir -p bin
	$(CC) -o $@ $^ $(LIB) $(LDFLAGS) $(CFLAGS)

obj/%.o: %.cpp
	@[ -d obj ] || mkdir -p obj
	$(CC) $(CFLAGS) $(INCLUDE) -o $@ -c $<

all: $(target)

clean:
        rm -f $(objects) $(target)

コンパイルする。

make

実行時に共有ライブラリのパスを設定

コンパイルした実行可能ファイルを実行する際には、共有ライブラリのパスを環境変数LD_LIBRARY_PATHに設定する。

export LD_LIBRARY_PATH=/usr/local/lib:$PYENV_ROOT/versions/anaconda3-5.0.1/lib:$LD_LIBRARY_PATH

実行

$ bin/a
123

成功すれば、「123」と表示される。