为什么 C++ 比 Python 快?从编译器和解释器的角度分析差异


这个问题实际上可以转换成为什么编译语言要比脚本语言要快。要分析这个问题,我们首先需要知道编译语言和脚本语言的代码分别是怎么跑起来的,这中间又发生了什么?

C++ 编译流程

C++ 程序在编写完成或者做了更改后的第一次运行前,需要先编译。C++ 编译的整个过程分为 4 步:
在这里插入图片描述
注意在汇编前的程序都是文本,经过汇编,就转换为了二进制文件,C++ 中常说的机器码就是二进制,二进制文件就可以直接交由计算机运行了。接下来详细讲一下四个步骤中发生了什么:

  1. 预处理
    在预处理阶段,比如我们现在写了一个 main.cpp 文件进行预处理。预处理器会将头文件导入当前的代码,比如 iostream 或 string 等头文件。
    之后会将注释给删除,将代码中的宏替换为宏的定义。
    预处理直接对源文件进行处理,并不关注语法规则等,处理后的文件为 main.i。
  2. 编译
    在编译阶段,编译器会检查代码的语法是否正确,并且可以对代码进行优化(O1,O2 优化等),之后将 main.i 文件转换为汇编语言程序 main.s,汇编语言是一种相对底层的语言,它为各种不同的高级语言提供了统一的输出(JAVA,C/C++ 等)
  3. 汇编
    在汇编阶段,汇编器将 main.s 汇编语言文件转换为机器语言指令(也就是常说的二进制机器码),并将文件转换为 main.o。这时候如果用文本打开该文件,会得到一堆乱码,这是因为文本编辑器通常使用 UTF-8 的 ASCII 码进行编解码,而这些二进制文件是不适用的。
  4. 链接
    在链接阶段,链接器会将上述的 main.o 文件与库文件(后缀为 .a 或者 .lib)链接为一个应用程序,我们可以在执行链接时指定需要使用的库。
    链接分为静态链接和动态链接,静态链接就是简单地将所需要链接的库的内容全部加入目标可执行程序中。静态链接的问题在于在一个计算机中如果存在多个相同程序,那么这些程序都会拷贝这个静态库,相当于内存中会存在多份静态库的拷贝,相当占用内存!另外,静态库对程序的更新会带来麻烦,如果静态库更新了,所有使用它的应用程序都需要重新编译再发布给用户。
    而与之相对的是动态链接,动态链接不会讲库拷贝到程序中,而是在应用程序中添加所调用库的描述信息(一般是库函数的重定位信息),执行的时候就会根据库的重定位信息调用库函数。所以动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库更新时带来的麻烦。用户只需在运行程序时更新动态库即可。

应用程序包含的是机器码,也就是二进制数据,这些二进制指令是可以直接交由计算机操作的,是一种最底层语言,因此效率很高。而且多次执行相同的程序,只有第一次需要编译。

Python 代码执行流程

Python 之所以被称为脚本语言,是因为其不需要在运行前编译,而是在运行过程中“像脚本一样”被解释器逐句转换为机器码(二进制),这样的话相当于每次执行 Python 脚本,都要再进行一次转换为二进制的过程,因此在这一点上 Python 就已经慢了很多。

C++ 与 Python 效率比较

言归正传,所以这两种语言的效率差异到底体现在什么地方呢?

首先第一点就是上文提到的,每次执行 Python 脚本,都要再由解释器进行一次转换为二进制的过程,而 C++ 只有第一次需要编译,后续都可以直接执行机器码。

这里我们可以举一个例子:

编写 Python 脚本时不需要声明类型,而是交由解释器进行动态类型检查。Python 官方指定的解释器为 CPython,CPython 是基于 C 实现的,在 CPython 中,每个对象底层都是一个 Obj* 指针类型,这个指针可以指向任意一种类型,所以所以它可以指向任意的对象,因此Python无法做基于类型方面的优化。以变量a + b为例,这个a和b指向的对象可以是整型、浮点型、字符串、列表、元组、甚至是我们自己实现了某个方法的类的实例对象。在计算 a+b 时,首先 Python 要判断变量到底指向的是什么类型,这在 C 级至少需要一次属性查找。然后 Python 将每一个操作都抽象成了一个方法,所以实例相加时要在对应的类型对象中找到该方法对应的函数指针,这又是一次属性查找。找到了之后将a、b作为参数传递进去,这会发生一次函数调用,会将 a 和 b 中维护的值拿出来进行运算,然后根据相加结果创建一个新的对象,再返回其对应的 PyObject * 指针。而对于 C++ 来讲,由于已经规定好了类型,所以a + b在编译之后就是一条简单的机器指令,所以两者在效率上差别很大。

第二点是 C++ 在编译阶段是可以进行优化的,C++ 编译器通常默认是 O0 优化,这是使用了最快的编译时间,在这种优化模式下:

  1. 所有变量都存在内存中,只有运算结果会放在 CPU 的寄存器上,所以会有较多的内存读写操作。
  2. 禁用其他的优化,会尽可能按照用户代码生成指令。

O1 优化:

  1. 循环变量通过寄存器计算,不再写入内存(比如 for (int i = 0; i < 10; ++i) 中的 i)

之后还有 O2 和 O3 优化,但是因为这些优化比较激进,会大幅增加编译的时间,而且编译后的文件难以调试,一般比较少用到。

总之,C++ 中存在编译过程的优化,而 Python 并没有,这又在另一个维度上让 C++ 快于 Python。

总结

本文从 C++ 和 Python 的运行过程入手,从编译器和解释器的差异分析了为什么 C++ 会比 Python 的运行速度快。在现代的企业场景中,追求极致代码运算效率的场景通常还是使用的 C++,比如服务器端编程、游戏编程等。

猜你喜欢

转载自blog.csdn.net/weixin_41670608/article/details/126535408