C++通過pybind11調用Python 實現transpose

在某些場合需要在C++實現類似numpy的numpy.transpose(a, axes)功能,但是很多庫如NumCpp都沒有提供這樣的方法,只有二維矩陣的轉置,沒法進行多維矩陣任意維度的轉換。

比較簡單的想法就是利用numpy現有的功能,在c++代碼裏面通過調用python來調用Numpy的transpose。

直接調用Python提供的原生API接口很麻煩,採用了pybind11可以顯著簡化調用,特別是涉及到傳遞numpy和list數據上。

直接用numpy的transpose,因爲該函數僅僅是改變array的strides,並不影響內存排布,替換的解決方案則是可以使用TensorFlow的transpose函數,可以得到改變內存排布的結果。後面想到了一個直接使用numpy的簡單方法:np做transpose之後reshape(-1)變成一維再reshape到結果維度,可以得到期望stride的內存排布。或者類似Pytorch的contiguous使用ascontiguousarray。

代碼如下,讀者如果運行可能需要適當修改CMakeLists.txt的include和library path,同時export PYTHONPATH包含.py文件的路徑。

cpp main.cpp

 
 
#include <iostream>
 
#include <string>
 
#include <vector>
 
using namespace std;
 
 
 
#include <pybind11/pybind11.h>
 
#include <pybind11/stl.h>
 
#include <pybind11/numpy.h>
 
#include <pybind11/embed.h> // everything needed for embedding
 
namespace py = pybind11;
 
 
 
bool Transpose(float* pOutData, float* pInData, vector<int>& inDataShape, vector<int>& perm) {
 
 
 
// start the interpreter and keep it alive
 
py::scoped_interpreter guard{};
 
 
 
// construct numpy array
 
py::array_t<float> npInputArray(inDataShape, pInData);
 
 
 
py::module calc = py::module::import("math_test");
 
auto func = calc.attr("transpose");
 
 
 
py::object result;
 
try {
 
result = func(npInputArray, perm);
 
} catch (std::exception& e) {
 
cout << "call python transpose failed:" << e.what() << endl;;
 
return false;
 
}
 
 
 
py::array_t<float> outArray = result.cast<py::array_t<float>>();
 
 
 
// copy output data
 
py::buffer_info outBuf = outArray.request();
 
float* optr = (float*)outBuf.ptr;
 
 
 
memcpy(pOutData, optr, outArray.size() * sizeof(float));
 
 
 
// remove ddata manually, result in double free
 
// if (!outArray.owndata()) {
 
// py::buffer_info buf = outArray.request();
 
// float* ptr = (float*)buf.ptr;
 
// delete [] ptr;
 
// }
 
 
 
return true;
 
}
 
 
 
int main(int argc, char* argv[]) {
 
 
 
vector<float> inVec = {0, 1, 2, 3, 4, 5, 6, 7};
 
vector<int> shape = {2, 2, 2};
 
vector<int> perm = {2, 1, 0 };
 
 
 
vector<float> outVec = inVec;
 
 
 
Transpose(outVec.data(), inVec.data(), shape, perm);
 
 
 
cout << "in data:" << endl;
 
for (int elem : inVec) {
 
cout << elem << " ";
 
}
 
cout << endl;
 
 
 
cout << "out data:" << endl;
 
for (int elem : outVec) {
 
cout << elem << " ";
 
}
 
cout << endl;
 
 
 
return 0;
 
}
 
 

python math_test.py

 
 
def transpose(data, perm):
 
import numpy as np
 
result = np.transpose(data, perm)
 
resultn = result.reshape(-1).reshape(result.shape)
 
return resultn
 
 

CMakeLists.txt

 
 
cmake_minimum_required(VERSION 3.10)
 
 
 
project(cmake_study LANGUAGES CXX)
 
 
 
set(CMAKE_CXX_STANDARD 11)
 
 
 
# add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0)
 
 
 
add_executable(main
 
main.cpp
 
)
 
 
 
target_include_directories(main
 
PUBLIC
 
/usr/include/python3.6m/
 
/mnt/d/codes/cpp/call_python/pybind11-2.6.1/include
 
)
 
target_link_libraries(
 
main
 
PUBLIC
 
/usr/lib/x86_64-linux-gnu/libpython3.6m.so
 
)
 
 

一些坑

這個工程單獨是可以work的,也就是單純的cpp單向調用python是可行的,但是如果在python調用cpp代碼,而這個cpp代碼通過pyblind再調用Python其他模塊時行不通。例如我可能是python 啓動了tensorflow,然後再tf cpp插件裏面調用了numpy的功能(tf的cpp插件調用python的包不要再調用tensorflow)。

針對原生python c api有如下解決方案(不需要Py_Initialize(); Py_Finalize();):

 
 
PyGILState_STATE gstate;
 
gstate = PyGILState_Ensure();
 
 
 
/* Perform Python actions here. */
 
result = CallSomeFunction();
 
/* evaluate result or handle exception */
 
 
 
/* Release the thread. No Python API allowed beyond this point. */
 
PyGILState_Release(gstate);
 
 

經過驗證上面的方法對於Pybind11也是適用的,也就是對  py::scoped_interpreter guard{};進行一個替換。

測試代碼:

 
 
 
 
class PyGil {
 
public:
 
PyGil() {
 
gstate = PyGILState_Ensure();
 
}
 
~PyGil() {
 
PyGILState_Release(gstate);
 
}
 
private:
 
PyGILState_STATE gstate;
 
};
 
 
 
void testPy() {
 
cout << "test py begin" << endl;
 
 
 
PyGil gil;
 
// PyGILState_STATE gstate;
 
// gstate = PyGILState_Ensure();
 
 
 
py::module calc = py::module::import("tensorflow");
 
py::print("Hello, world!");
 
 
 
// PyGILState_Release(gstate);
 
cout << "test py end" << endl;
 
}
 
 

pybind11 cpp調用python的其他案例

pybind11調用函數傳遞字符串的example:(可見直接傳遞string即可,無需做轉換,極大簡化了調用過程)

 
 
bool TestStr() {
 
 
 
// start the interpreter and keep it alive
 
py::scoped_interpreter guard{};
 
 
 
string inStr = "hello world";
 
 
 
py::object result;
 
try {
 
py::module calc = py::module::import("math_test");
 
auto func = calc.attr("test_str");
 
result = func(inStr);
 
} catch (std::exception& e) {
 
cout << "call python func failed:" << e.what() << endl;;
 
return false;
 
}
 
string outStr = result.cast<string>();
 
 
 
cout << "out str:" << outStr << endl;
 
return true;
 
}
 
 
 
 

總結

從cpp單向通過pybind11調用python時獲取Lock用py::scoped_interpreter guard{};,而如果在Python調用cpp,在這個cpp反向再次調用Python可以用上面PyGil 的方式通過gstate = PyGILState_Ensure(); PyGILState_Release(gstate);來實現。

List/vector, string直接傳參就好,numpy數組傳遞轉成py::array_t。

pybind11調用相對模塊使用xx.yy方式。

 

參考資料

https://www.jianshu.com/p/c912a0a59af9

https://stackoverflow.com/questions/44659924/returning-numpy-arrays-via-pybind11

https://gist.github.com/terasakisatoshi/79d1f656be9023cc649732c5162b3fc4

https://pybind11.readthedocs.io/en/stable/advanced/embedding.html

 

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章