C++编译/链接库/神经网络框架与Cuda、Cudnn的前世今生
#### C/C++编译链接
C语言的编译链接过程就是把我们所编写的C/C++代码转换成相应硬件架构能够执行的指令集中的机器指令,整个过程主要分为编译和链接,编译的主要工作是将源代码转换为目标文件(.o文件),链接链接是把目标文件、操作系统的启动代码和用到的库文件进行组织,形成最终生成可执行文件(.exe文件)的过程。
-
编译
-
编译预处理
- 宏定义指令
如# define Name TokenString,# undef(在自身后面取消以前定义的宏定义)等。对于前一个伪指令,预编译所要做的是将程序中的所有Name用TokenString替换,但作为 字符串常量的 Name则不被替换。对于后者,则将取消对某个宏的定义,使以后该串的出现不再被替换。 - 条件编译指令
如# ifdef,# ifndef,# else,# elif,# endif等。这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。 - 头文件包含指令
处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。(头文件展开是个递归展开的过程,被包含的文件可能还包含其他文件)
- 删除注释
- 添加行号和文件名标识
- 保留所有#pragma指令
预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。下一步,此输出文件将作为编译程序的输入而被翻译成为机器指令.
- 宏定义指令
-
编译
编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码(.s文件)。优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。 -
汇编
汇编过程实际上指把汇编语言代码翻译成目标机器指令(.o文件)的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。
-
-
链接
由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。
链接可以在以下三个时刻进行,并分为静态链接和动态链接:
编译时(compilation time),也就是在源代码被翻译成机器代码时。
加载时(load time)在程序被加载器loader加载到内存并执行时。
运行时(run time),由应用程序负责加载。
开发深度学习的小伙伴对LD_LIBRARY_PATH这个路径一定很好奇,我想他便是来源于加载时。-
静态链接
- 执行时间:程序编译阶段
- 具体流程:在这种链接方式下,函数的代码将从其所在的静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。
-
动态链接
- 执行时间:推迟到运行时再进行(load time)
- 具体流程: 在可执行文件装载时或运行时,由操作系统的装载程序加载库。这里的库指的是动态链接库,Windows下以.dll为后缀,Linux下以.so为后缀。值得一提的是,在Windows下的动态链接也可以用到.lib为后缀的文件,但这里的.lib文件叫做导入库,是由.dll文件生成的。
-
静态链接
动态链接库与静态链接库的区别
- 后缀命名不同
win平台下,静态链接库的后缀为.lib,动态链接库的后缀为.dll
linux平台下, 静态链接库的后缀为.a,动态链接库的后缀为.so - 位置存放不同
动态链接使用动态链接库,允许可执行模块(.dll文件或.exe文件)仅包含在运行时定位DLL中函数的可执行代码所需的信息。
静态链接使用静态链接库,链接器从静态链接库LIB获取所有被引用函数,并将库同代码一起放到可执行文件中。 - 静态链接库的使用
- 需要的文件: 头文件 .h 、静态库 .lib 头文件.h中有函数的声明,使用静态链接库的项目需要引用该文件才能编译通过
- .lib包含了实际执行代码、符号表等等
- 加载lib的方法: 法1.使用编译链接参数或者VS的配置属性来设置 法2.使用pragma编译语句,例如pragma comment(lib,“a.lib”)
- .lib中的指令将全部被直接包含在最终生成的 EXE 文件中
-
动态链接库的使用
- 隐式调用
- 需要的文件: 头文件 .h 、动态链接库的.lib文件,动态链接库的dll文件
- 头文件.h和静态链接库使用时的作用一样,使用动态链接库中的函数的项目需要引用该文件才能编译通过
- .lib包含了函数所在的DLL文件和文件中函数位置的信息,.dll包含了实际执行代码、符号表等等
- 加载lib的方法:lib是编译链接是用的,跟使用静态链接库时一样有两种方法:法1.使用编译链接参数或者VS的配置属性来设置 法2.使用pragma编译语句,例如pragma comment(lib,“a.lib”)
- 加载dll的方法:dll是运行时用的,链接了lib之后形成的EXE可执行文件中已经有了dll的信息,所以只要把dll放在和exe同一个目录下就可以了,运行时根据EXE需要自动加载dll中的函数
- 显式调用
- 需要的文件: 动态链接库的dll文件 不需要.h头文件和.lib文件,因为LoadLibrary之后可以使用getProcAddress来查找一个函数的地址从而调用该函数
- (显式调用的前提是使用者需要知道想调用的函数的名字、参数、返回值信息,也就是说虽然编译链接用不上.h头文件,但是调用者编程时可能还是要看.h文件作参考来知道函数名字、参数、返回值信息)
Cuda与Cudnn以及Cudatoolikit的区别
参考: 一文弄懂Cuda
- CUDA:为“GPU通用计算”构建的运算平台。 cudnn:为深度学习计算设计的软件库。
- CUDA Toolkit (nvidia):CUDA完整的工具安装包,其中提供了 Nvidia 驱动程序、开发 CUDA 程序相关的开发工具包等可供安装的选项。包括 CUDA 程序的编译器、IDE、调试器等,CUDA 程序所对应的各式库文件以及它们的头文件。
- CUDA Toolkit (Pytorch):CUDA不完整的工具安装包,其主要包含在使用 CUDA 相关的功能时所依赖的动态链接库。不会安装驱动程序。、
- NVCC 是CUDA的编译器,只是 CUDA Toolkit 中的一部分)
- Cudnn中包含了用于深度学习加速的动态库,一般需要将解压缩以后的Cudn动态链接库文件复制到cuda文件中,容易遗忘的一个步骤是使用Idconfig将其设置为共享,不然还是没法用。
注:CUDA Toolkit 完整和不完整的区别:在安装了CUDA Toolkit (Pytorch)后,只要系统上存在与当前的 cudatoolkit 所兼容的 Nvidia 驱动,则已经编译好的 CUDA 相关的程序就可以直接运行,不需要重新进行编译过程。如需要为 Pytorch 框架添加 CUDA 相关的拓展时(Custom C++ and CUDA Extensions),需要对编写的 CUDA 相关的程序进行编译等操作,则需安装完整的 Nvidia 官方提供的 CUDA Toolkit。
神经网络框架(tensorflow/pytorch/)编译时Cuda路径设置问题
-
# Pytorch 实际使用的运行时的 cuda 目录 import torch.utils.cpp_extension torch.utils.cpp_extension.CUDA_HOME # 编译该 Pytorch release 版本时使用的 cuda 版本 import torch torch.version.cuda
-
pytorch/其他深度学习网络框架寻找cuda的过程
- 环境变量的作用
- 存储各种工具、命令的路径,当使用工具或者命令的时候,系统回去PATH中查找对应的工具与命令
- 当自己下载了某个工具或者自己写了某个可执行程序,想要不加路径直接执行,则需要将该工具、程序的路径添加入PATH中
- 如何查看环境变量
- vim ~/.bashrc
- echo $PATH
- export
- pytorch查找可用CUDA的过程
- 环境变量CUDA_HOME 或 CUDA_PATH
- /usr/local/cuda
- which nvcc的上级上级目录(which nvcc 会在环境变量PATH中找)
- 如果上述都不存在,则torch.utils.cpp_extension.CUDA_HOME为None,会使用conda安装的cudatoolkit,其路径为cudart 库文件目录的上级目录(此时可能是通过 conda 安装的 cudatoolkit,一般直接用 conda install cudatoolkit,就是在这里搜索到 cuda 库的)。
- CUDA路径设置方式
-
固定式
-
软链接式
- bashrc设置
export CUDA_HOME=/usr/local/cuda export PATH=$PATH:$CUDA_HOME/bin export LD_LIBRARY_PATH=/usr/local/cuda/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
- cuda版本查看
cat /usr/local/cuda/version.txt CUDA Version 9.0.176
- cuda 切换
sudo rm -rf /usr/local/cuda #删除之前生成的软链接 sudo ln -s /usr/local/cuda-10.1 /usr/local/cuda #生成新的软链接
- cuda 再查看
cat /usr/local/cuda/version.txt CUDA Version 10.1.243
-
面向神经网络框架的Cuda及Cudnn的安装过程
-
-
选择相应版本(需要查找cuda、cudnn与神经网络框架的对应关系),一般选cuDNN Library for Linux (x86_64),安装简单。
-
解压
tar -xvf cudnn-11.2-linux-x64-v8.1.1.33.tgz
-
查看cuda路径
which nvcc 显示 /usr/local/cuda-11.1/bin/nvcc
-
拷贝动态链接库
cd cuda/ sudo cp include/cudnn* /usr/local/cuda-11.1/include sudo cp lib64/libcudnn* /usr/local/cuda-11.1/lib64 sudo chmod a+r /usr/local/cuda-11.1/include/cudnn* sudo chmod a+r /usr/local/cuda-11.1/lib64/libcudnn*
-
删除解压缩包
rm -rf cuda
-
-
确定系统内是否装有Nvidia的驱动,驱动版本向下兼容。
-
去官网下载cuda(使用run方式,比较容易),只需安装cuda toolikit.
wget https://developer.download.nvidia.com/compute/cuda/11.1.0/local_installers/cuda_11.1.0_455.23.05_linux.run sudo sh cuda_11.1.0_455.23.05_linux.run
-
cuda路径设置,参考第四小节的内容
-
Idconfig 命令的用途主要是在给定路径中搜索出可共享的动态链接库(格式如 lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件。
为了让动态链接库为系统所共享,需运行动态链接库的管理命令ldconfig ,此执行程序存放在/sbin 目录下。 ldconfig 通常在系统启动时运行,而当用户安装了一个新的动态链接库时,就需要手工运行这个命令。
ldconfig 通常在系统启动时运行,而当用户安装了一个新的动态链接库时,就需要手工运行这个命令。