jvm jni 及 pvm pybind11 大批量數據傳輸及優化
PS:要轉載請註明出處,本人版權所有。
PS: 這個只是基於《我自己》的理解,
如果和你的原則及想法相衝突,請諒解,勿噴。
前置說明
本文作為本人csdn blog的主站的備份。(BlogID=116)
環境說明
- android 手機
- linux python環境
前言
近幾個月來,對我來說,發生了許許多多的事情,導致有很多idea,但是都未形成好的文章。最近,趁著這個機會,寫一篇。
由於業務的安排,我們需要在c/c++層與java和python層進行數據交換,數據量有大有小,但是由於我們業務上對這個數據交換的延時有一定的要求,因此有些問題需要我們解決。在我們的實驗過程中,我們發現了在常規情況下,在jvm中用新創建ByteArray/FloatArray進行大數據量(6Mb byte/2Mb floats)的傳輸,時間在5ms/7ms,在pvm中用新創建bytearray大數據量(8Mb byte)的傳輸,時間在1ms左右。從實驗情況來看,我們需要優化jvm中進行大數據量傳輸的方法。
我以前寫過關於java,python和c/cpp交互的一些文章,感興趣可以參考。
- 《C++ 調用 Python 總結(一)》 //blog.csdn.net/u011728480/article/details/103903612
- 《java 手動生成jni頭文件(JNI靜態註冊)》 //blog.csdn.net/u011728480/article/details/87260113
- 《Android JNI靜態和動態註冊 、Java Reflect(C或C++層反射和JAVA層反射)、Java 可變參數(JNI實現)》 //blog.csdn.net/u011728480/article/details/78963494
jvm jni篇
jni常規大量數據交換方法網上有許多,基本都是如下所示:
在java往c/cpp返回時,一般都是獲取數據的底層地址,然後針對地址操作即可。
jbyteArray array;//or jfloatArray array; passed by jni-func
void * _you_wanted_ptr = env->GetPrimitiveArrayCritical(array, nullptr);
// TODO
env->ReleasePrimitiveArrayCritical(array, _you_wanted_ptr, JNI_ABORT);
在c/cpp往java傳輸大量數據時,有兩種方式,一種是直接new一個數組,然後返回的方式,一種就是獲取java層的數組地址,然後直接修改相關的數據即可。其基本如下所示:
// slow way
int len = xxx;
void * data_ptr = xxx;
jXXXArray array = env->NewXXXArray(len);
env->SetXXXArrayRegion(array, 0, len, (const jXXX *) data_ptr);
return array;
// fast way
jbyteArray array;//or jfloatArray array; passed by jni-func
int len = xxx;
void * data_ptr = xxx;
env->SetXXXArrayRegion(array, 0, len, (const jXXX *) data_ptr);
這裡在使用fast way模式後,在jvm中用進行大數據量(6Mb byte/2Mb floats)的傳輸,時間在0.88ms/1ms,注意,有使用限制。這裡一定要注意多執行緒安全的問題。
pvm pybind11篇
在pybind11中,大規模數據傳輸一般有兩種數據結構,一種是py::bytes,一種就是我們常見的numpy數組,特別是在影像處理中,numpy數組是最常見的一種格式。下面,根據這兩種方式,分別介紹。
py::bytes 類型傳輸
python 層傳給c/cpp。
const py::bytes &value;//passed by pybind11-func
Py_ssize_t size = PyBytes_GET_SIZE(value.ptr());
char * ptr = PyBytes_AsString(value.ptr());
//TODO
c/cpp 層傳給python。
char * buf = xxx;
int len = xxx;
return py::bytes(buf, len);//In pybind11, return to pvm
注意,在py::bytes中,也有直接修改地址的方式,這裡就不提供了(python buffer protocol),有心人自己去研究吧。
numpy數據傳輸
這個也有像py::bytes那樣創建數組,然後返回的方式,這裡就不提供了。這裡主要還是演示一下怎麼快速在c/cpp中獲取numpy數據。其實這裡的數據傳輸也就是直接獲取numpy數組地址,基本大差不差。
c/cpp到python
// python buffer protocol
py::array_t<float, py::array::c_style | py::array::forcecast> &buffer;//passed by pybind11-func
auto buf_info = buffer.unchecked<1>();
char * ptr = (char *)buf_info.data(0)
// set value to ptr(numpy)
// get value from ptr(numpy)
注意,這裡使用到一個叫做python buffer protocol的東西,有興趣大家可以看看,我在這個上並沒有深究。
pybind11中記憶體管理問題
在pybind11中,要小心管理記憶體,特別是注意以下兩種調用的區別。
根據//pybind11.readthedocs.io/en/stable/advanced/classes.html#non-public-destructors的說明,我們一般會有兩種情況需要選擇使用。
// 單例
class MyClass{
private:
~MyClass(){}
};
// 禁止unique_ptr 調用 析構函數, 所有資源釋放需要在cpp側進行完成。
py::class_<MyClass, std::unique_ptr<MyClass, py::nodelete>>(m, "MyClass")
.def(py::init<>())
// 一般class
class MyClass{
public:
~MyClass(){}
};
// unique_ptr 析構時自動調用析構函數,所有資源釋放由unique_ptr完成。
py::class_<MyClass, std::unique_ptr<MyClass>>(m, "MyClass")
.def(py::init<>())
後記
總的來說,在jvm和pvm中,通過操作固定數組的底層指針,我們可以快速的獲取數據和傳輸數據。但是存在一些現象,例如需要注意一些原子操作和pvm/jvm中數組的生命周期的問題,我這裡建議,如果是大規模數據傳輸,建議直接全局數組,這樣保證生命周期問題。
參考文獻
[1]//pybind11.readthedocs.io/en/stable/advanced/classes.html#non-public-destructors

PS: 請尊重原創,不喜勿噴。
PS: 要轉載請註明出處,本人版權所有。
PS: 有問題請留言,看到後我會第一時間回復。