tensorflow源码解析1--多语言接口:从python到C

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_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)加载了动态链接库_pywrap_tensorflow_internal.so 并赋值给了_pywrap_tensorflow_internal.
_pywrap_tensorflow_internal.so中包含了tf核心库的所有二进制代码, python端应用层代码只需要加载该动态链接库就可以运行起来了.

使用_pywrap_tensorflow_internal.__dict__查看该对象的内置属性:

image.png

可以看到该实体包含了许多方法, 包括TF_Run.

再来看C语言端接口文件pywrap_tensorflow_internal.cc, 该文件中有一个静态函数符号表SwigMethods(如下图所示), 将python接口端文件中的函数名TF_Run和C语言端接口函数_wrap_TF_Run映射:

image.png

_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_文件名这个函数

image.png

_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找到这两个函数的符号如下:

image.png

尝试通过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加载的动态链接库可以直接使用函数名来调用函数

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容