tensorflow源碼解析1--多語言接口:從python到C

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_internalpywrap_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)置屬性:

image.png

可以看到該實體包含了許多方法, 包括TF_Run.

再來看C語言端接口文件pywrap_tensorflow_internal.cc, 該文件中有一個靜態(tài)函數(shù)符號表SwigMethods(如下圖所示), 將python接口端文件中的函數(shù)名TF_Run和C語言端接口函數(shù)_wrap_TF_Run映射:

image.png

_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ù)

image.png

_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ù)的符號如下:

image.png

嘗試通過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ù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子乓序,更是在濱河造成了極大的恐慌,老刑警劉巖歹鱼,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谈喳,死亡現(xiàn)場離奇詭異,居然都是意外死亡钮孵,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門眼滤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巴席,“玉大人,你說我怎么就攤上這事诅需⊙Γ” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵堰塌,是天一觀的道長赵刑。 經(jīng)常有香客問我,道長场刑,這世上最難降的妖魔是什么般此? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上铐懊,老公的妹妹穿的比我還像新娘邀桑。我一直安慰自己,他們只是感情好科乎,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布壁畸。 她就那樣靜靜地躺著,像睡著了一般茅茂。 火紅的嫁衣襯著肌膚如雪捏萍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天空闲,我揣著相機與錄音令杈,去河邊找鬼。 笑死进副,一個胖子當(dāng)著我的面吹牛这揣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播影斑,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼给赞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了矫户?” 一聲冷哼從身側(cè)響起片迅,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎皆辽,沒想到半個月后柑蛇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡驱闷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年耻台,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片空另。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡盆耽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出扼菠,到底是詐尸還是另有隱情摄杂,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布循榆,位于F島的核電站析恢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏秧饮。R本人自食惡果不足惜映挂,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一泽篮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧袖肥,春花似錦咪辱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至寸癌,卻和暖如春专筷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蒸苇。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工磷蛹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人溪烤。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓味咳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親檬嘀。 傳聞我的和親對象是個殘疾皇子槽驶,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容