都说tensorflow底层矩阵相关的操作是由eigen库来实现的,一直都没搞明白到底tensorflow是怎么调用库中函数的。
所以最近研究了一下它们之间的关系,使用的是tensorflow2.0
首先,为了搞清楚tensorflow是如何使用eigen进行矩阵分割和调度的,对tensorflow2.0的源码中的threadpool.cc进行了研究,该文件除了包含tensorflow/core/…中的一些头文件之外,还包含了
include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor"
这一句话,包含了一个没有后缀的 名为Tensor的文件
打开源码中的该文件(CXX11/Tensor)康一康,内容为
#include "unsupported/Eigen/CXX11/Tensor"
#ifdef _WIN32
#ifndef SLEEP_FUNC_HEADER_GUARD
#define SLEEP_FUNC_HEADER_GUARD
inline void sleep(unsigned int seconds) { Sleep(1000*seconds); }
#endif
// On Windows, Eigen will include Windows.h, which defines various
// macros that conflict with TensorFlow symbols. Undefine them here to
// prevent clashes.
#undef DeleteFile
#undef ERROR
#undef LoadLibrary
#endif // _WIN32
发现该文件又include了自身,所以一直有所困惑。
实际上,该文件include的 #include "unsupported/Eigen/CXX11/Tensor"这句话并不是包含该文件(15行代码的这个文件)本身,而是包含的eigen库中对应位置的Tensor文件。
找到下载的eigen3.3.9目录,在以下位置找到实际上包含的Tensor文件
G:\cpp_files\eigen-3.3.9\eigen-3.3.9\unsupported\Eigen\CXX11\Tensor
该文件一共有154行代码(C++),其中包括了eigen库中多个执行实际功能的.h文件
位置在:
G:\cpp_files\eigen-3.3.9\eigen-3.3.9\unsupported\Eigen\CXX11\src\Tensor\“xxx”.h
所以可以看出,tensorflow在编译的时候是自动下载eigen库,并且因为已经提前知道eigen库的目录结构,所以在源码文件编写时也是直接include未来需要用到的eigen库的文件,比如一直研究的threadpool.cc中的函数逻辑,就需要eigen中的很多.h文件,所以使用了include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor"这句话。
另外,像上述提到的Tensor这样的没有后缀名的文件在eigen库中有很多,为什么有了可以执行实际功能的.h文件之后还需要新建这样的总结性文件呢,应该是为了让他人便于include,因为这样只需要include一个(没有后缀的module)文件,而如果不这么做可能需要include几十个.h(执行实际功能的文件)
由于tensorflow编译时才下载eigen库的代码,所以研究其逻辑实现的时候需要同时结合tensorflow2.0的源码和下载好的eigen库的代码。
另外,在研究已经编译好的tensorflow2.0的core目录时,即
G:\software\anaconda\envs\tf2.0\Lib\site-packages\tensorflow_core\……
发现其中的include目录下,即
G:\software\anaconda\envs\tf2.0\Lib\site-packages\tensorflow_core\include\……
内部的文件夹:Eigen、unsupported与直接下载下来的eigen库的完全相同
除此之外,其中的文件夹:thrid_party\eigen3,与tensorflow2.0源码中的eigen3文件夹(目录为:G:\cpp_files\tensorflow2.0\tensorflow\third_party\eigen3)的内容完全一致
打开编译后的eigen3中的某个文件,比如
G:\software\anaconda\envs\tf2.0\Lib\sitepackages\tensorflow_core\include\third_party\eigen3\unsupported\Eigen\CXX11\Tensor
即文章开头说到的那个源码中没有后缀名的Tensor文件,其内容为:
#include "unsupported/Eigen/CXX11/Tensor"
#ifdef _WIN32
#ifndef SLEEP_FUNC_HEADER_GUARD
#define SLEEP_FUNC_HEADER_GUARD
inline void sleep(unsigned int seconds) { Sleep(1000*seconds); }
#endif
// On Windows, Eigen will include Windows.h, which defines various
// macros that conflict with TensorFlow symbols. Undefine them here to
// prevent clashes.
#undef DeleteFile
#undef ERROR
#undef LoadLibrary
#endif // _WIN32
可以看出,内容没有改变,与源码中相同,说明该文件(以及该文件夹中的所有编译不需要的文件)在编译之后都保留了下来。
观察该文件的第一行代码,#include “unsupported/Eigen/CXX11/Tensor”,使用的是“”型的include方式,意思是在当前工作目录去include某个文件(与<>型include不同),因为它include的是
G:\software\anaconda\envs\tf2.0\Lib\sitepackages\tensorflow_core\include\unsupported\Eigen\CXX11\Tensor
文件,所以可以知道设定的工作目录是红色的部分。
再随便点开几个
G:\software\anaconda\envs\tf2.0\Lib\sitepackages\tensorflow_core\include\third_party\eigen3
文件夹中的文件(该文件夹就是编译过程中保留下来的文件夹)
可以发现其中的没有后缀名的文件中的代码都是在include工作目录下
Eigen 或者unsupported
文件夹中的文件而没有实际的执行文件。
由此可知,tensorflow调用eigen库的方式为:源码中直接include eigen库中需要的部分,直接使用其函数等逻辑操作,并且include时不是直接include eigen库中直接含有函数实现的.h头文件,而是include eigen库中(官方给)提供的没有后缀名的module,通过这种方式一次性大批量地include有函数实现的.h头文件。
之后,在编译时下载需要的eigen模块,即eigen库中的Eigen文件夹和unsupported文件夹。
最后,tensorflow安装完成后,因为tensorflow源码中的逻辑都是include的eigen的module文件(无后缀的文件),所以这些“转接口文件”(third_party\eigen3)还需要保留,用来在代码的依赖层面将tensorflow的C++代码与eigen库的含有实际可执行函数代码的.h文件链接起来。