tf使用了SWIG生成C语言端接口文件pywrap_tensorflow_internal.cc
和python端接口文件pywrap_tensorflow_internal.py
, 但网上大多数的教程都没详细说明这两个文件是如何影响python端和C语言端的调用的.
在本篇文章中, 让我们仔细看看这两个文件是如何发挥作用的吧.
我们从python的API层一直往下追踪, 应用层代码如下:
import tensorflow as tf
x = tf.constant(1)
sess = tf.Session()
import pdb
pdb.set_strace() #打断点
print(sess.run(x))
我们使用pdb, 从sess.run(x)
开始追踪调用栈直到python层的最底层, 我们看到sess.run()
最后调用了python端接口文件pywrap_tensorflow_internal.py
中的TF_Run, TF_Run定义如下:
def TF_Run(session, handle, feed_dict, output_names, out_status):
return _pywrap_tensorflow_internal.TF_Run(session, handle, feed_dict, output_names, out_status)
TF_Run = _pywrap_tensorflow_internal.TF_Run
可以看到pywrap_tensorflow_internal.py
中 TF_Run的定义是_pywrap_tensorflow_internal.TF_Run
.
_pywrap_tensorflow_internal
在pywrap_tensorflow_internal.py
的开始部分引入, 定义如下:
def swig_import_helper():
from os.path import dirname
import imp
fp = None
try:
fp, pathname, description = imp.find_module('_pywrap_tensorflow_internal', [dirname(__file__)])
except ImportError:
import _pywrap_tensorflow_internal
return _pywrap_tensorflow_internal
if fp is not None:
try:
_mod = imp.load_module('_pywrap_tensorflow_internal', fp, pathname, description)
finally:
fp.close()
return _mod
_pywrap_tensorflow_internal = swig_import_helper()
可以看到, 该文件通过imp.load_module('_pywrap_tensorflow_internal', fp, pathname, description)
加载了动态链接库_pywrap_tensorflow_internal.so
并赋值给了_pywrap_tensorflow_internal
.
_pywrap_tensorflow_internal.so
中包含了tf核心库的所有二进制代码, python端应用层代码只需要加载该动态链接库就可以运行起来了.
使用_pywrap_tensorflow_internal.__dict__
查看该对象的内置属性:
可以看到该实体包含了许多方法, 包括TF_Run
.
再来看C语言端接口文件pywrap_tensorflow_internal.cc
, 该文件中有一个静态函数符号表SwigMethods
(如下图所示), 将python接口端文件中的函数名TF_Run
和C语言端接口函数_wrap_TF_Run
映射:
_wrap_TF_Run
是SWIG为tensorflow::TR_Run_wrapper
自动生成的接口函数, 还是位于pywrap_tensorflow_internal.cc
文件.
_wrap_TF_Run
函数会解析python应用层传下来的参数,并转发给tensorflow::TR_Run_wrapper
函数.然后_wrap_TF_Run
将该函数的返回值封装成python应用层可接收的参数类型并return.
现在大致厘清了C语言接口端文件pywrap_tensorflow_internal.cc
和python端接口文件pywrap_tensorflow_internal.py
的作用
但还是有一些比较细节的问题没有完全搞清楚, 比如在python层最后的_pywrap_tensorflow_internal.TF_Run
中, python是如何从动态链接库的静态函数表SwigMethods
中找到TF_Run
的映射关系的?
分析这个比较困难, 我一开始的思路是通过gdb调试python代码, 找到python加载so文件函数的最底层并直接进入到so文件中, 但是在python使用gdb真的是太难用了, 就放弃了使用调试分析这条路.
后来我发现利用Cython也可以将python代码打包生成的so文件, 并通过imp加载,python打包生成so文件_打工人胖子的博客-CSDN博客_python so
我就想通过查找SWIG生成的so文件和Cython代码生成的so文件中有哪些函数是一样的, 从而找出一些共性点。
通过nm查看, 发现它们共有的是PyInit_文件名
这个函数
_add.so
是我用SWIG自己生成的so文件, tf的so太复杂了, 所以我写了一个只包含两个函数的c文件来测试SWIG
test.cpython-35m-x86_64-linux-gnu.so
是通过Cython生成的so文件
我们来测试一下PyInit
是不是imp
加载so文件的关键函数,下面我直接用C语言,通过gcc -shared -fPIC
生成一个so文件,并在python中使用imp加载该文件,发现报错:
Traceback (most recent call last):
File "/home/zqzhou/test/test.py", line 35, in <module>
test = imp.load_module('libtest', fp, pathname, description)
File "/home/zqzhou/miniconda3/envs/tf/lib/python3.5/imp.py", line 243, in load_module
return load_dynamic(name, filename, file)
File "/home/zqzhou/miniconda3/envs/tf/lib/python3.5/imp.py", line 344, in load_dynamic
return _load(spec)
File "<frozen importlib._bootstrap>", line 693, in _load
File "<frozen importlib._bootstrap>", line 666, in _load_unlocked
File "<frozen importlib._bootstrap>", line 577, in module_from_spec
File "<frozen importlib._bootstrap_external>", line 938, in create_module
File "<frozen importlib._bootstrap>", line 222, in _call_with_frames_removed
ImportError: dynamic module does not define module export function (PyInit_libtest)
可以看到最后一行报错dynamic module does not define module export function (PyInit_libtest)
由此可见, 通过imp加载so文件直接使用函数名来调用函数的话必须定义函数PyInit_文件名
,
我们来具体可看PyInit中干了什么, 下面是简化版的函数定义
PyObject* PyInit__add(void) {
PyObject *m, *d, *md;
static struct PyModuleDef SWIG_module = {
PyModuleDef_HEAD_INIT,
(char *) SWIG_name,
NULL,
-1,
SwigMethods, //SwigMethods是上文提到了静态函数符号表, SWIG_module中包含了py端接口函数名与c语言端接口函数名的映射关系
NULL,
NULL,
NULL,
NULL
};
/* Fix SwigMethods to carry the callback ptrs when needed */
SWIG_Python_FixMethods(SwigMethods, swig_const_table, swig_types, swig_type_initial);
m = PyModule_Create(&SWIG_module);
md = d = PyModule_GetDict(m);//由此处可知通过m中包含一个字典类型的映射关系的,
(void)md;
SWIG_InitializeModule(0);
return m;//将m返回给上层的python,
}
PyInit返回的m包含着python端函数名和C语言端函数名的映射关系, python解析该关系之后就可以在python段直接通过函数名调用C语言端的函数了.
至此, 我们已经梳理完了从python端到C端的整个流程
其他
tf中使用了imp来读取SWIG生成的动态链接库, 实际上对于定义了PyInit_文件名
的so文件, 可以直接通过import导入:
import _pywrap_tensorflow_internal as tf //加载_pywrap_tensorflow_internal.so
print(tf.TF_Run) //运行成功
总结: SWIG生成的So文件中包含特殊的PyInit
函数, python端通过imp加载该so文件, 并调用PyInit函数可以获取到python端函数名到C语言端函数的映射关系,从而使得用户可以在python应用层直接通过使用python端的函数名来调用C语言端的函数.
(选读)潜入动态链接库
实际上, 上文提到的通过imp和import导入动态链接库已经和我们在C/C++中经常使用的加载动态链接库的方法不同了
我们看看python中使用C/C++style 风格的加载动态链接库的方式.
现在, C语言文件test.c中有个叫test的函数声明如下:
void test();
通过gcc -share -fPIC test.c -o libtest.so 生成libtest.so文件,
在python中使用loadlibrary函数加载该so文件, 并直接使用函数名test
调用
from ctypes import *
test = cdll.LoadLibrary("./libtest.so")
test.test()
发现调用成功
但如果我们使用g++ -share -fPIC test.c -o libtest.so
生成so文件, 发现test函数的调用失败, 错误信息如下
Traceback (most recent call last):
File "/home/zqzhou/test/test.py", line 9, in <module>
test.test()
File "/usr/lib/python3.5/ctypes/__init__.py", line 360, in __getattr__
func = self.__getitem__(name)
File "/usr/lib/python3.5/ctypes/__init__.py", line 365, in __getitem__
func = self._FuncPtr((name_or_ordinal, self))
AttributeError: ./libtest.so: undefined symbol: test //找不到符号
通过nm libtest.so
找到函数test
编译后的符号为_Z4testv
,使用该符号可以成功调用函数:
from ctypes import *
test = cdll.LoadLibrary("./libtest.so")
test._Z4testv()//成功运行
现在我们尝试调用tf的so文件中的两个函数, 使用nm
找到这两个函数的符号如下:
尝试通过loadlibrary直接加载tf的so文件, 调用相关函数:
from ctypes import *
test = cdll.LoadLibrary("./_pywrap_tensorflow_internal.so")
test._ZN10tensorflow29CudaSupportsHalfMatMulAndConvEv #成功, 该函数为gobal
test._wrap_CudaSupportsHalfMatMulAndConv#失败, 该函数为local
test.CudaSupportsHalfMatMulAndConv#失败, 找不到该函数
函数是global还是local可以通过readelf或者nm命令查看(readelf对名字过长的符号会截断, 还是用nm比较好)
tf中使用imp加载tf的so文件可以直接通过函数名调用so内的函数:
import imp
from os.path import dirname
fp, pathname, description = imp.find_module('_pywrap_tensorflow_internal', [dirname(__file__)])
print(fp, pathname, description)
test = imp.load_module('_pywrap_tensorflow_internal', fp, pathname, description)
fp.close()
test.CudaSupportsHalfMatMulAndConv #可以
test._ZN10tensorflow29CudaSupportsHalfMatMulAndConvEv # 不可以
可以发现, 通过imp加载的动态链接库可以直接使用函数名来调用函数