将棋でディープラーニングを試しているが、Pythonで入力データの加工を行うと処理速度が問題になっている。
そこで、PythonからC++で作成したモジュールを呼び出して、その中でnumpyのオブジェクトの加工を行いたい。
PythonからC++の呼び出しはオーバーヘッドがあるため、頻繁に呼び出すとかえって遅くなる。
そのため、ミニバッチのデータをまとめて作成するようにする予定。
以下の手順で、PythonでC++のモジュールを使うことができた。
環境
- Windows Home 64bit
- Python 3.5.2(Anaconda 4.2.0)
- Boost 1.64.0
- Visual Studio 2015
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.]