TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

PythonからC++を呼び出してnumpyを使う

将棋でディープラーニングを試しているが、Pythonで入力データの加工を行うと処理速度が問題になっている。

そこで、PythonからC++で作成したモジュールを呼び出して、その中でnumpyのオブジェクトの加工を行いたい。
PythonからC++の呼び出しはオーバーヘッドがあるため、頻繁に呼び出すとかえって遅くなる。
そのため、ミニバッチのデータをまとめて作成するようにする予定。

以下の手順で、PythonでC++のモジュールを使うことができた。

環境

Boostのダウンロード

Boostの1.63以降でBoost.Numpyが標準で利用できるようになっている。
公式サイトから最新の1.64.0をダウンロードした。
ダウンロードしたファイルを任意のディレクトリに展開する。
以下、C:\boost_1_64_0に展開したものとして記述する。

Boost.Pythonのビルドを有効にする

ホームディレクトリC:\Users\xxxxに「user-config.jam」を作成し、以下の通り編集する。

using python : 3.5 : C:\\Anaconda3\\python ;

※C:\\Anaconda3のパスは環境に合わせて編集する。

Boostのビルド

cd /d C:\boost_1_64_0
bootstrap.bat
b2 toolset=msvc threading=multi variant=debug,release link=static runtime-link=static address-model=64 --stagedir=stage/x64 -j 8

64ビットのスタティックリンクライブラリを生成するオプションを指定している。
ダイナミックリンクライブラリでは、numpyのdtypeがグローバル変数に定義されているため、DLLの内の変数がリンクできないため、np::dtype::get_builtin()などを使うとリンクエラーとなる。

stage\x64\libに.libが生成される。

python関連の.libファイルの名前を以下の通り変更する。

libboost_python3-vc140-mt-s-1_64.lib → libboost_python-vc140-mt-s-1_64.lib
libboost_python3-vc140-mt-sgd-1_64.lib → libboost_python-vc140-mt-sgd-1_64.lib
libboost_numpy3-vc140-mt-s-1_64.lib → libboost_numpy-vc140-mt-s-1_64.lib
libboost_numpy3-vc140-mt-sgd-1_64.lib → libboost_numpy-vc140-mt-sgd-1_64.lib

※2017/10/27 追記
Version 1.65.1では名前の変更は不要になっていました。

C++のDLLプロジェクト作成

Visual Studio 2015でWin32のDLLプロジェクトを作成する。
アクティブな構成をx64/Releaseにする。

プロジェクトのプロパティでインクルードディレクトリに以下を追加する。

  • C:\Anaconda3\include
  • C:\boost_1_64_0

ライブラリディレクトリに以下を追加する。

  • C:\Anaconda3\libs
  • C:\boost_1_64_0\stage\x64\lib

C/C++->コード生成のランタイムライブラリをマルチスレッド(/MT)に変更する。
boostをスタティックリンクライブラリとして生成しているので合わせる必要がある。

C++のコード作成

以下のようなコードを作成する。
(コードはこちらのサイトを参考にした。)

mymod1.cpp
#define BOOST_PYTHON_STATIC_LIB
#define BOOST_NUMPY_STATIC_LIB
#include <boost/python/numpy.hpp>
#include <stdexcept>
#include <algorithm>

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

/* 2倍にする */
void mult_two(np::ndarray a) {
	int nd = a.get_nd();
	if (nd != 1)
		throw std::runtime_error("a must be 1-dimensional");
	size_t N = a.shape(0);
	if (a.get_dtype() != np::dtype::get_builtin<float>())
		throw std::runtime_error("a must be float32 array");
	float *p = reinterpret_cast<float *>(a.get_data());
	std::transform(p, p + N, p, [](float x) { return 2 * x; });
}

BOOST_PYTHON_MODULE(mymod1) {
	Py_Initialize();
	np::initialize();
	p::def("mult_two", mult_two);
}

スタティックリンクをするには、ヘッダーを読み込む前に以下の定義が必要
#define BOOST_PYTHON_STATIC_LIB
#define BOOST_NUMPY_STATIC_LIB

ビルドする。

プロジェクトのx64/Releaseに.dllが作成される。

Pythonコード作成

以下のようなコードを作成する。

mymod.py
import mymod1
import numpy as np

if __name__ == '__main__':
    a = np.array([1,2,3], dtype=np.float32)
    mymod1.mult_two(a)
    print(a)

上記でビルドした.dllファイルをpythonのコードと同じディレクトリにコピーし、ファイル名をmymod1.pydに変更する。
拡張子は.pydである必要がある。

Visual Studioのビルドイベント->ビルド後のイベントで、

copy $(TargetPath) $(SolutionDir)\PythonApplication1\mymod1.pyd

など設定すればよい。

Pythonのコードを実行する。

python mymod.py
実行結果
[ 2.  4.  6.]