闹麻了,因为各种原因,现在需要重新回顾一下Python,话不多说,开始吧
1. Python是解释型语言 && Python与C++代码执行过程的区别:
(1)C++
源码(Source):C++的源码文件是 .cpp 文件
预处理(PreProcess):生成 .i 文件
预处理的操作有 处理 #include
、#define
等宏指令,
编译(Compile):将.cpp文件 编译为 .s 文件,此时的.s 文件是汇编文件,无法被CPU执行
编译加速方式有:
汇编(Assemble):将汇编文件.s 文件 转换为可以被CPU理解的 机器码 .o 文件
但此时的字节码文件仅仅将源码中包含的代码转化为了字节码,但由于源码中一般会涉及到链接库、标准库来支持代码的组织编写,因此,此时的字节码虽然可以被CPU理解,但无法运行。因为源码中链接库的位置 只是被填充上了 未解析的符号 ,此种情况下直接运行 .o 文件会报错 “未定义符号错误(undefined reference)。”
坚持要运行汇编生成的字节码文件也可以,通过 linux 的 ld 链接库工具,手动链接所需的库,如 `ld final_exe src.o XXX` 。对应的链接库位置被对应的符号填充
此外,.o 文件也没有指定程序入口位置,只是一个没有框架的代码片段,,这也是它不能被CPU直接执行的原因。
这时候是可以使用 ld 指令进行手动链接的指定的库,如下指令所示,来避免上述的问题,生成可执行文件
//通常编译会这样做:
gcc main.c -o main # 完整的编译+链接,生成可执行文件// -------------------------------------------------------------------//但如果我们只编译不链接
gcc -c main.c -o main.o # 只编译,生成目标文件 main.o// 这时候 main 变成了可执行文件,可以直接运行:
// -lc 含义是链接到C语言标准库 --dynamic-linker 是在指定动态链接库
ld -o main main.o -lc --dynamic-linker /lib64/ld-linux-x86-64.so.2// 顺利执行 main文件
./main
连接(Link):将源码与涉及的库相链接,生成可执行代码 .exe 文件或ELF
(2)Python
源码(Source):Python的源码文件是 .py 文件
解析(Parse):Python 解释器 将源码 解析为 语法树AST
编译(Compile):Python编译器将 语法树 编译为 字节码 (.pyc)
注意,此时的 字节码 并非是可以直接被CPU理解执行的内容,.pyc 仅可以被Python虚拟机所理解的中间形式
解释执行(Interpretation):Python 虚拟机(PVM)逐行将 字节码 进行解释执行
(3)区别:
对于C++来说,C++的编译器编译后会生成 可以被 CPU 直接理解执行 的 机器码,此时的CPU无需做其他处理,直接执行即可。因此,C++ 是编译型语言
对于Python来说,Python的编译器仅仅会将Python的源码转换为可供Python虚拟机理解的字节码 .pyc 文件,且对于Python语言来说,只需要执行 ` python test.py ` 即可,Python的编译过程对用户透明,这也是为什么 Python 被称为 ”无显式编译“。编译生成的字节码 需要Python虚拟机PVM 逐行解释执行 .pyc 文件的字节码内容,而不会变成直接可以被CPU使用的机器码。
PVM接收到字节码文件,会逐行将字节码翻译成机器码,交给CPU使用。
注意,PVM是一个由C语言写的软件,运行在CPU上
2. 为什么 Python 比 C++ 慢?
(1)Python需要PVM一行行将字节码转换为机器码,而C++会一次性在编译过程中生成机器码
(2)Python需要动态内存回收,而C++可以手动指定
(3)Python的数据类型是运行时间决定的,而C++则是静态类型,优化更快
3. 如何加速Python的运行速度:
(1)使用JIT (Just in Time 编译方式),减少PVM的翻译成本
使用pypy代替CPython,将热点代码提前编译为机器码
使用Numba 加速,将数学计算部分提前编译为机器码
(2)核心代码使用C++编写,Python只做封装:
TensorFlow 底层使用C++,Python只做封装;
Numpy 的计算单元用C实现
(3) CPython
将Python代码转为C代码,再编译为机器码,加速执行