修論がだんだん忙しくなってきた中の人です.研究でPythonからC++のコードをたたく必要が出てきたのでいろいろお試ししていたのですが,その際にいろいろ詰まったので書き残しておきます.今回のコードはこちらに上げてあります.
1.PythonからC++をさわる
Python側からC++のコードを実行する方法はPython.hやSWIG,Boost.Python,pybind11など様々なものがあります(参考1).今回の研究で使用している環境(HLSツールや実行環境)の問題で,以下の制約がありました.
- 相互呼び出しの機能は不要(PythonからC++を実行できればOK)
- STLコンテナが使えない(≒std::vectorなどを使う必要がない)
- g++やCPythonに標準で入っているライブラリのみで行けると非常に楽
いろいろ調べた結果,とりあえずctypesを使ってみることにしました.
2.C++で共有ライブラリを作成する
今回は以下のC++コードをPythonから呼び出します.
// MAC_VEC.hpp
#include <cstdint>
namespace MAC_VEC{
template<typename data_t>
void mac_vec(uint32_t size, data_t a[], data_t x[], data_t b[], data_t y[]){
for(uint32_t i = 0; i < size; i++){
y[i] = a[i] * x[i] + b[i];
}
}
}
ctypesを使う場合テンプレートをそのまま呼び出すことはできないので,型を確定させるラッパーを作っておきます.
// MAC_VEC_WRAPPER.cpp
#include <cstdint>
#include "MAC_VEC.hpp"
extern "C" {
void MAC_VEC_FP32(uint32_t size, float a[], float x[], float b[], float y[]){
MAC_VEC::mac_vec<float>(size, a, x, b, y);
}
void MAC_VEC_INT16(uint32_t size, int16_t a[], int16_t x[], int16_t b[], int16_t y[]){
MAC_VEC::mac_vec<int16_t>(size, a, x, b, y);
}
void MAC_VEC_UINT32(uint32_t size, uint32_t a[], uint32_t x[], uint32_t b[], uint32_t y[]){
MAC_VEC::mac_vec<uint32_t>(size, a, x, b, y);
}
}
とりあえずfloat(fp32),int16, uint32の3パターン準備しました.この時extern "C"をつけないと,コンパイラ側でシンボル名を書き換える操作を行うため,Python側から読めなくなることがあります(参考2).
この二つが準備できたら,g++で共有ライブラリ(.so)を生成します.
// generate_so.sh
g++ MAC_VEC_WRAPPER.cpp -shared -o MAC_VEC.so
コンパイルが通ると,MAC_VEC.soが生成されるはずです.
3.Pythonから動かす
先ほど作成したライブラリをPythonから動かしてみましょう.今回使用するctypesは,Python2.5から標準で入っているライブラリです.
この記事ではMAC_VEC_FP32のみを扱います.残りの二つのに関しては,こちらの実装を確認してください.
// C_API_TEST.py
import ctypes
# load .so file
libc = ctypes.cdll.LoadLibrary("./MAC_VEC.so")
# define params and src/dst data
len_vec = 8
coef = 1.1
vec_x = [i * coef for i in range(len_vec)]
vec_a = [2 * coef for i in range(len_vec)]
vec_b = [(i - 8) * coef for i in range(len_vec)]
vec_y = [0 for i in range(len_vec)]
# define data type and convert method
len_vec_type = ctypes.c_uint32
data_type = ctypes.c_float
vec_type = data_type * len_vec
# convert list-type data
_len_vec = len_vec_type(len_vec)
_vec_x = vec_type(*vec_x)
_vec_a = vec_type(*vec_a)
_vec_b = vec_type(*vec_b)
_vec_y = vec_type(*vec_y)
# execution
libc.MAC_VEC_FP32(_len_vec, _vec_a, _vec_x, _vec_b, _vec_y)
# convert results to list-type
result = list(_vec_y)
print(result)
配列の型を宣言するあたりの作法で詰まりました.配列のデータ型×配列の要素数で表現し,アンパックしたlistを投げ込むことで変換できるようです.
実行すると,こんな感じの結果が得られました.
実行結果.INT16とUINT32の結果も表示している. |
とりあえず正しい結果が得られているようです.
4. まとめ
今回はPythonからC++の生配列をいじってみました.ライブラリの追加も最小限で済み,割と簡単に動かすことができました.少し凝ったデータのやり取りをしようとすると大変かもしれないですが,C/C++標準のデータ型であれば十分に使えると思います.
©2021 shts All Right Reserved.
このコメントは投稿者によって削除されました。
返信削除