tf使用了SWIG生成C語言端接口文件pywrap_tensorflow_internal.cc
和python端接口文件pywrap_tensorflow_internal.py
, 但網(wǎng)上大多數(shù)的教程都沒詳細說明這兩個文件是如何影響python端和C語言端的調(diào)用的.
在本篇文章中, 讓我們仔細看看這兩個文件是如何發(fā)揮作用的吧.
我們從python的API層一直往下追蹤, 應(yīng)用層代碼如下:
import tensorflow as tf
x = tf.constant(1)
sess = tf.Session()
import pdb
pdb.set_strace() #打斷點
print(sess.run(x))
我們使用pdb, 從sess.run(x)
開始追蹤調(diào)用棧直到python層的最底層, 我們看到sess.run()
最后調(diào)用了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)
加載了動態(tài)鏈接庫_pywrap_tensorflow_internal.so
并賦值給了_pywrap_tensorflow_internal
.
_pywrap_tensorflow_internal.so
中包含了tf核心庫的所有二進制代碼, python端應(yīng)用層代碼只需要加載該動態(tài)鏈接庫就可以運行起來了.
使用_pywrap_tensorflow_internal.__dict__
查看該對象的內(nèi)置屬性:
可以看到該實體包含了許多方法, 包括TF_Run
.
再來看C語言端接口文件pywrap_tensorflow_internal.cc
, 該文件中有一個靜態(tài)函數(shù)符號表SwigMethods
(如下圖所示), 將python接口端文件中的函數(shù)名TF_Run
和C語言端接口函數(shù)_wrap_TF_Run
映射:
_wrap_TF_Run
是SWIG為tensorflow::TR_Run_wrapper
自動生成的接口函數(shù), 還是位于pywrap_tensorflow_internal.cc
文件.
_wrap_TF_Run
函數(shù)會解析python應(yīng)用層傳下來的參數(shù),并轉(zhuǎn)發(fā)給tensorflow::TR_Run_wrapper
函數(shù).然后_wrap_TF_Run
將該函數(shù)的返回值封裝成python應(yīng)用層可接收的參數(shù)類型并return.
現(xiàn)在大致厘清了C語言接口端文件pywrap_tensorflow_internal.cc
和python端接口文件pywrap_tensorflow_internal.py
的作用
但還是有一些比較細節(jié)的問題沒有完全搞清楚, 比如在python層最后的_pywrap_tensorflow_internal.TF_Run
中, python是如何從動態(tài)鏈接庫的靜態(tài)函數(shù)表SwigMethods
中找到TF_Run
的映射關(guān)系的?
分析這個比較困難, 我一開始的思路是通過gdb調(diào)試python代碼, 找到python加載so文件函數(shù)的最底層并直接進入到so文件中, 但是在python使用gdb真的是太難用了, 就放棄了使用調(diào)試分析這條路.
后來我發(fā)現(xiàn)利用Cython也可以將python代碼打包生成的so文件, 并通過imp加載,python打包生成so文件_打工人胖子的博客-CSDN博客_python so
我就想通過查找SWIG生成的so文件和Cython代碼生成的so文件中有哪些函數(shù)是一樣的, 從而找出一些共性點。
通過nm查看, 發(fā)現(xiàn)它們共有的是PyInit_文件名
這個函數(shù)
_add.so
是我用SWIG自己生成的so文件, tf的so太復(fù)雜了, 所以我寫了一個只包含兩個函數(shù)的c文件來測試SWIG
test.cpython-35m-x86_64-linux-gnu.so
是通過Cython生成的so文件
我們來測試一下PyInit
是不是imp
加載so文件的關(guān)鍵函數(shù),下面我直接用C語言,通過gcc -shared -fPIC
生成一個so文件,并在python中使用imp加載該文件,發(fā)現(xiàn)報錯:
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文件直接使用函數(shù)名來調(diào)用函數(shù)的話必須定義函數(shù)PyInit_文件名
,
我們來具體可看PyInit中干了什么, 下面是簡化版的函數(shù)定義
PyObject* PyInit__add(void) {
PyObject *m, *d, *md;
static struct PyModuleDef SWIG_module = {
PyModuleDef_HEAD_INIT,
(char *) SWIG_name,
NULL,
-1,
SwigMethods, //SwigMethods是上文提到了靜態(tài)函數(shù)符號表, SWIG_module中包含了py端接口函數(shù)名與c語言端接口函數(shù)名的映射關(guān)系
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中包含一個字典類型的映射關(guān)系的,
(void)md;
SWIG_InitializeModule(0);
return m;//將m返回給上層的python,
}
PyInit返回的m包含著python端函數(shù)名和C語言端函數(shù)名的映射關(guān)系, python解析該關(guān)系之后就可以在python段直接通過函數(shù)名調(diào)用C語言端的函數(shù)了.
至此, 我們已經(jīng)梳理完了從python端到C端的整個流程
其他
tf中使用了imp來讀取SWIG生成的動態(tài)鏈接庫, 實際上對于定義了PyInit_文件名
的so文件, 可以直接通過import導(dǎo)入:
import _pywrap_tensorflow_internal as tf //加載_pywrap_tensorflow_internal.so
print(tf.TF_Run) //運行成功
總結(jié): SWIG生成的So文件中包含特殊的PyInit
函數(shù), python端通過imp加載該so文件, 并調(diào)用PyInit函數(shù)可以獲取到python端函數(shù)名到C語言端函數(shù)的映射關(guān)系,從而使得用戶可以在python應(yīng)用層直接通過使用python端的函數(shù)名來調(diào)用C語言端的函數(shù).
(選讀)潛入動態(tài)鏈接庫
實際上, 上文提到的通過imp和import導(dǎo)入動態(tài)鏈接庫已經(jīng)和我們在C/C++中經(jīng)常使用的加載動態(tài)鏈接庫的方法不同了
我們看看python中使用C/C++style 風(fēng)格的加載動態(tài)鏈接庫的方式.
現(xiàn)在, C語言文件test.c中有個叫test的函數(shù)聲明如下:
void test();
通過gcc -share -fPIC test.c -o libtest.so 生成libtest.so文件,
在python中使用loadlibrary函數(shù)加載該so文件, 并直接使用函數(shù)名test
調(diào)用
from ctypes import *
test = cdll.LoadLibrary("./libtest.so")
test.test()
發(fā)現(xiàn)調(diào)用成功
但如果我們使用g++ -share -fPIC test.c -o libtest.so
生成so文件, 發(fā)現(xiàn)test函數(shù)的調(diào)用失敗, 錯誤信息如下
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
找到函數(shù)test
編譯后的符號為_Z4testv
,使用該符號可以成功調(diào)用函數(shù):
from ctypes import *
test = cdll.LoadLibrary("./libtest.so")
test._Z4testv()//成功運行
現(xiàn)在我們嘗試調(diào)用tf的so文件中的兩個函數(shù), 使用nm
找到這兩個函數(shù)的符號如下:
嘗試通過loadlibrary直接加載tf的so文件, 調(diào)用相關(guān)函數(shù):
from ctypes import *
test = cdll.LoadLibrary("./_pywrap_tensorflow_internal.so")
test._ZN10tensorflow29CudaSupportsHalfMatMulAndConvEv #成功, 該函數(shù)為gobal
test._wrap_CudaSupportsHalfMatMulAndConv#失敗, 該函數(shù)為local
test.CudaSupportsHalfMatMulAndConv#失敗, 找不到該函數(shù)
函數(shù)是global還是local可以通過readelf或者nm命令查看(readelf對名字過長的符號會截斷, 還是用nm比較好)
tf中使用imp加載tf的so文件可以直接通過函數(shù)名調(diào)用so內(nèi)的函數(shù):
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 # 不可以
可以發(fā)現(xiàn), 通過imp加載的動態(tài)鏈接庫可以直接使用函數(shù)名來調(diào)用函數(shù)