为什么需要

计算机只能执行机器码(二进制指令)。

而人类写代码用的是高级语言。编译器和解释器就是把高级语言转成可执行形式的中介。

编译器(Compiler)

工作方式:把整个源程序一次性翻译成目标代码(机器码或中间代码),然后执行。

流程(简化):

  1. 词法分析:把字符流切分成 token(关键字、标识符、运算符等);
  2. 语法分析:根据语法规则构建抽象语法树(AST);
  3. 语义分析:类型检查、作用域检查等;
  4. 中间代码生成;
  5. 优化:常量折叠、死代码消除、循环优化、内联等;
  6. 目标代码生成:生成机器码(或汇编);
  7. 链接:把多个目标文件和库合并成可执行文件。

代表:C、C++、Rust、Go。

优点:执行快(编译期完成优化,运行时直接执行机器码)。

缺点:编译需要时间;跨平台需要重新编译。

解释器(Interpreter)

工作方式:逐行读取源代码,边解释边执行。

代表(经典):早期的 BASIC、Shell 脚本。

优点:启动快,跨平台容易(同一份源码在不同平台的解释器上运行)。

缺点:执行慢(每次执行都要重新分析源码)。

现代语言:混合方案

纯粹的编译或解释已经少见,主流是混合:

一、字节码 + 虚拟机

代表:Java(JVM)、Python(CPython)、C#(.NET CLR)。

流程:

  1. 源代码编译成字节码(Bytecode)——一种平台无关的中间表示;
  2. 虚拟机(VM)解释执行字节码,或进一步编译成机器码。

优势:

  • 跨平台:字节码是统一的,各平台只需实现虚拟机("Write Once, Run Anywhere");
  • 比纯解释快(字节码更接近机器指令,省去了重复的语法分析)。

二、即时编译(JIT,Just-In-Time)

思路:运行时把"热点代码"编译成机器码。

流程:

  1. 先解释执行(或执行字节码);
  2. 监控哪些代码被频繁执行(热点);
  3. 把这些热点代码动态编译成本地机器码;
  4. 后续执行直接使用编译后的机器码。

代表:Java HotSpot JVM、JavaScript 的 V8 引擎、.NET。

优势:

  • 结合了编译的速度和解释的灵活性;
  • 可以进行基于运行时信息的优化(这是静态编译做不到的)——比如发现某个函数在运行时总是收到整数参数,就生成针对整数的特化版本。

代价:启动时需要"预热"(JIT 编译需要时间),因此短生命周期的程序(如一次性脚本)可能感觉不到 JIT 的好处。

三、提前编译(AOT,Ahead-Of-Time)

与 JIT 相对:在程序运行之前就编译成机器码。

代表:Go、Rust、C/C++,以及 Java 的 GraalVM Native Image、.NET 的 AOT。

优势:启动快(无需预热),适合短生命周期进程和Serverless 场景(冷启动时间敏感)。

一个对比

特性纯编译纯解释字节码+JIT
执行速度最快慢接近编译(预热后)
启动速度慢(要编译)快中等(要预热)
跨平台差好好
运行时优化无无有

一个实用含义

Python 慢的原因:CPython 是解释执行字节码的,且是动态类型——每次操作都要做类型检查和方法查找。

优化路径:

  • PyPy:带 JIT 的 Python 实现,对纯 Python 代码可达数倍加速;
  • NumPy:把密集计算交给底层的 C/Fortran 实现;
  • Cython / C 扩展:把热点代码编译成机器码;
  • Numba:用装饰器把数值函数 JIT 编译。

说明:本文为编译原理的知识整理。