🧩 第五章 理解内存结构:程序员的基本素养
📄 一句话概括

编译一次跑 2000 次 vs 每次编译再跑 2000 次,实测多翻译了 1999 次——这就是【重复翻译】的代价。

📝 本节和前面几节的关系

5.1 讲了调用的四步,这一节往上退一层:源码怎么变成能跑的东西。

一、两种方式

编译器:先一次性把源码翻译成机器码,然后执行 N 次。翻译付一次。 解释器:每跑一次就翻译一次。翻译付 N 次。 两者的差别不在语言强弱,在【翻译的钱付几次】。

二、实测

同一段源码跑 2000 次:编译版翻译 1 次、执行 2000 次;不编译版翻译 2000 次、执行 2000 次。 多翻译了 1999 次——执行次数一样,翻译次数差两千倍。 总时间也跟着涨了一大截(具体倍数见输出)——涨的就是那 1999 次翻译。 所以【解释慢】不是慢在执行,是慢在重复翻译。

三、第三条路:混合

编译(C/C++/Go):翻译一次到机器码,快,但换平台要重编。 解释(早期 BASIC):每次翻译,慢,但改一行立刻能跑。 混合(Java/Python):先编译成中间代码,再反复执行它——翻译付一次,跨平台的好处还在。 今天的主流语言基本都走第三条。

四、为什么混合赢了

因为它同时拿到了两头的便宜:翻译只付一次,中间代码不绑 CPU。 代价是中间隔了一层:执行时还要由虚拟机翻译(5.3 讲这个)。 所以现代语言的速度问题,本质是【虚拟机翻译得好不好】。

五、动手 Demo:两种运行方式——编译器与解释器

同一段源码跑 2000 次,一次先编译、一次每次编译。 数一数各翻译了几次、各花了多少时间。

import time
src = "s = 0\nfor i in range(200):\n    s += i\n"
N = 2000
code = compile(src, "<src>", "exec")

t0 = time.perf_counter()
for _ in range(N):
    exec(code, {}, {})      # 翻译 1 次
t1 = time.perf_counter()
for _ in range(N):
    exec(src, {}, {})       # 每次翻译
t2 = time.perf_counter()
print(t1 - t0, t2 - t1)

实跑输出:

--------------------------------------------------------------------
两种运行方式——编译器与解释器
--------------------------------------------------------------------

  同一段源码要跑 2000 次,**编译一次和每次编译差多少?**真的跑一遍:

      编译版     翻译    1 次,执行 2000 次    0.041 秒
      不编译     翻译 2000 次,执行 2000 次    0.093 秒

      多翻译了                    1999 次
      总时间多了                  2.3 倍

  方式     翻译几次             执行几次     典型代表
  --------------------------------------------------------
  编译     1 次                 N 次         C/C++/Go
  解释     N 次                 N 次         早期 BASIC
  混合     1 次(到字节码)     N 次         Java/Python

  **实测多出来的那 1999 次翻译,就是总时间多出来的那一截。**
  **所以现代语言大多走第三条路:先翻译成中间代码,再反复执行它。**
  **这就是 5.3 的虚拟机。**

实测:翻译 1 次 vs 2000 次,多出来 1999 次——执行都是 2000 次,差的全在翻译上。 所以【解释慢】慢在重复翻译——这也是现代语言都走【编译到中间代码】的原因,下节讲。

30 秒实验

  1. 用 compile() 和直接 exec 字符串各跑一次,自己量一下倍数。
  2. 想一想:为什么改了代码不用重启的语言,多半是解释型的。

常见误区

  1. 以为编译型一定比解释型强。它们是在不同的地方付钱。
  2. 以为 Python 完全不编译。它先编译成字节码,只是不落成机器码。
自测三问(点开对答案) 1. 编译器和解释器的根本差别是什么? —— 翻译的钱付几次:编译器一次编译、执行 N 次;解释器每次执行都要翻译一次。 2. 实测差在哪? —— 翻译次数 1 次对 2000 次,多出来 1999 次——执行次数一样,翻译差两千倍,总时间因此涨一大截。 3. 混合路线为什么成了主流? —— 先编译成中间代码再反复执行:翻译只付一次,中间代码又不绑 CPU,两头的便宜都拿到。

相关笔记