C和Python执行过程
C文件
- 预处理:将#include头文件以及宏定义替换成其真正的内容,预处理之后得到的仍然是文本文件,但文件体积会大很多。
- 编译:将经过预处理之后的文件转换成汇编代码。
- 汇编:将汇编代码转换成机器码,产生的文件叫做目标文件,为二进制格式。
- 链接:将多个目标文件以及所需的库文件(.so等)链接成最终的可执行文件。
Python文件
- 解释:由Python解释器(CPython,JPython等)将Python源代码解释为字节码,由Python虚拟机(PVM)执行这些字节码。字节码在PVM里对应的是PyCodeObject对象,在磁盘上的表现形式为
.pyc
文件 - 编译:PVM在运行时将字节码根据需求编译为机器码,而非在运行前编译所有字节码。
Python慢的原因
- 从上述的执行过程我们可以得出,在运行Python文件时,多出了解释的步骤,并且运行时才进行编译,所以执行效率低于C/C++。
- 此外,由于最常见的Python解释器为CPython,其GIL锁机制导致Python程序在运行时只有一个线程可以在cpu上执行,即使机器有多个核,因为GIL是全局锁,只有拿到GIL锁的线程才可以运行,其他的核只能等待GIL释放, 即当前运行线程进行IO操作或者time ticks计数满100时。
(注意GIL锁是CPython解释器而非Python特性)
如何加速
针对GIL锁
- 对于IO密集型应用,由于GIL会由于频繁的IO操作而释放,所以多线程和多进程性能差异不大,由于线程内进行通讯和切换更方便且高效,所以还是可以使用线程锁。
- 对于计算密集型应用,可采用多进程或者协程来代替多线程。
针对Python解释和编译过程
- 采用Cython,将python文件翻译为C文件进行编译再执行。
- 采用Numba,Numba 是一个开源的JIT编译器,不同于CPython用c语言的方式来解释字节码,Numba将一部分Python和Numpy的字节码转化为转换为一种语法类似于汇编的低级编程语言LLVM IR(Intermediate Representation),由LLVM编译器将IR转换为字节码。在进行编译之前,LLVM Optimizer会对字节码进行优化。流程大致如下:
Numba的限制
- 不支持**kwargs
- 不支持with写法
- debug非常麻烦
- 只适合纯数字计算
针对NN模块的加速
Torchdynamo
TorchDynamo针对PyTorch操作进行优化,工作在字节码执行之前,对字节码进行改写,将其中的PyTorch操作提取出来形成一个FX Graph,这个FX Graph会由不同的编译后段进行JIT的编译和调优。
支持的编译后端包括:
[‘ansor’, ‘aot_autograd’, ‘autotune’, ‘cudagraphs’, ‘cudagraphs_ts’, ‘cudagraphs_ts_ofi’, ‘eager’, ‘fx2trt’, ‘inductor’, ‘ipex’, ‘nnc’, ‘nnc_ofi’, ‘nvfuser’, ‘nvfuser_ofi’, ‘ofi’, ‘onnx2tensorrt’, ‘onnx2tensorrt_alt’, ‘onnx2tf’, ‘onnxrt’, ‘onnxrt_cpu’, ‘onnxrt_cpu_numpy’, ‘onnxrt_cuda’, ‘static_runtime’, ‘taso’, ‘tensorrt’, ‘torch2trt’, ‘ts’, ‘tvm’]
Nebullvm
Nebullvm将深度学习模型进行优化,测试不同优化技术,包括选择不同的编译后端,量化,蒸馏等,确定不同模型在特定硬件上的最佳方式,实现2-30倍的提速。