Press "Enter" to skip to content

c++ 通过c api调用python跑cnn前向运算的一些问题

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

有比较成熟的c++ rpc框架,想要基于python代码快速搭建一个线上可用的rpc服务时,可用用c api的方式调用python脚本,将其封装成一个rpc服务。

 

这种方式的优点是相比将python代码翻译成tensorflow/pytorc的c++ 模块调用,开发成本要小很多;缺点是性能较低,只能做到单个并发,只适合于有成熟c++ rpc框架且无python版rpc框架的情况。

 

开发过程中有一些要注意的问题,在此记录一下。

 

 

embedding-python文档

 

初始化,python解释器在同一个线程中可以初始化多次,能正常返回,但是在不同线程初始化的适合,整个程序会直接core掉。另外初始化的时候有一套解释器拉起/so库选择的规则,在文档中有描述,使用的时候要格外注意。

 

全局锁

 

python c api线程安全文档

 

多线程调用c api封装的函数时,需要加全局锁,否则可能会导致程序挂掉。

 

参数传递

 

c api args文档

 

标注库string的传递,Py_BuildValue会根据\0自动判断c str的结尾,并传递给python

 

PyObject *pInitArgs = Py_BuildValue("(s)", _sModelPath.c_str());

 

二进制数据块的传递,此时注意标识不再是s了,而是s#,这样Py_BuildValue就不会再根据\0判断数据结束,而是根据后面传入的数据长度来判断。

 

PyObject *pArgs = Py_BuildValue("(s#)", c, audioBuf.size());

 

传递二进制数据加上别的格式的数据,将别的数据依次往后加就行了

 

PyObject *pArgs = Py_BuildValue("(s#,i)", c, audioBuf.size(), iAudioFormat);

 

结果解析

 

python c api数据结构描述文档

 

可参考此文档解析从python返回的数据结构,如解析元组类型:

 

if(pRet && PyTuple_Check(pRet) && PyTuple_Size(pRet) == 2){
    PyObject* a = PyTuple_GetItem(pRet, 0);
    PyObject* b = PyTuple_GetItem(pRet, 1);
    PyArg_Parse( a, "i", &c );
    PyArg_Parse( b, "i", &d);
} else {
    c = -1;
    d = -1;
}

 

引用计数

 

python c api引用计数文档

 

Py_Object是所有py c类型的基类,它保存了一个指向python对象的指针和对象的引用计数,在使用完后要调用一下Py_DECREF/Py_XDECREF来减一个引用计数,引用计数到0时会调用python对象的析构函数。其中Py_DECREF需要明确指针非null,而Py_XDECREF则无所谓。

 

cnn前向运算不能跨线程

 

这里遇到了一个奇怪的问题,python中cnn的前向运算无法跨线程,最开始试了TensorFlow,后来换成PyTorch也是一样。

 

具体表现是,在a线程中初始化python解释器,然后:

在a线程或者b线程中初始化tf/pytorch,加载模型,可以成功
在a线程调用前向运算的函数,可以成功
在b线程调用前向运算的函数,会一直卡在函数调用的位置,不抛异常,也不返回
在a/b线程中调用除了前向运算之外的其它函数,都可以成功

原因暂时没有去探究,先记录一下现象。所以这种做法只适合用来快速搭建demo。真正实现高性能服务的话还是要使用c++版本的tensorflow或者pytorch,来多线程调用,或者是run batch并行计算。

Be First to Comment

发表评论

电子邮件地址不会被公开。 必填项已用*标注