- 后端
- 文档
- 教程
【免费下载链接】system-design-101
Explain complex systems using visuals and simple terms. Help you prepare for system design interviews.
本指南以 data/guides/how-do-c++-java-python-work.md 为核心骨架,结合仓库中 Javascript 执行原理、垃圾回收机制、程序运行流程 等文档,系统讲解编译型、字节码型、解释型三类语言的执行模型。读完你将掌握 C++、Java、Python 从源码到 CPU 执行的全链路差异,理解 JIT(Just-In-Time)编译器为何能缩短解释型与编译型的性能差距,并能据此在不同业务场景下做出语言选型判断。
引言:同一份源码,三种不同的"翻译"方式
无论使用哪种编程语言,源代码最终都必须被"翻译"成 CPU 能够理解的机器指令。差别在于:翻译发生在什么时候、由谁完成、翻译的产物是什么。
现代编程语言大致可以分为三类执行模型:
- 编译型(Compiled):编译器预先将源码整体翻译成机器码,CPU 直接执行机器码。
- 字节码型(Bytecode / VM):编译器先将源码翻译成与平台无关的字节码,再由虚拟机(如 JVM)执行;部分热点代码会通过 JIT 编译成机器码加速。
- 解释型(Interpreted):不预先编译,由解释器在运行时逐条解析并执行源码。
关联文档指出:"Compiled languages in general run faster than interpreted languages"(编译型语言通常比解释型语言运行更快)。这正是三类模型最直观的性能差异,其根本原因在于翻译成本发生的时间点与执行时是否需要反复翻译。
编译型语言:C++ 的"一次翻译,永久执行"
核心流程:编译器把源码编译成机器码 → 机器码由 CPU 直接执行。
源代码 (hello.cpp) --编译器--> 目标文件 (.o) --链接器--> 可执行文件 --加载--> CPU 直接执行机器码关联文档给出的代表性编译型语言为C、C++、Go。
编译的完整链路
以 C++ 为例,"编译"并不是一步到位的,通常包含四个阶段:
| 阶段 | 输入 → 输出 | 作用 |
|---|---|---|
| 预处理(Preprocessing) | .cpp→.i | 展开#include、处理#define宏 |
| 编译(Compilation) | .i→.s | 将源码翻译为汇编语言 |
| 汇编(Assembly) | .s→.o | 将汇编翻译为机器码目标文件 |
| 链接(Linking) | .o→ 可执行文件 | 合并目标文件与库,解析符号引用,生成最终可执行文件 |
对应的常见命令行(以 GNU 工具链为例):
# 一步到位:预处理 + 编译 + 汇编 + 链接 g++ hello.cpp -o hello # 分阶段观察 g++ -E hello.cpp -o hello.i # 只看预处理结果 g++ -S hello.cpp -o hello.s # 生成汇编代码 g++ -c hello.cpp -o hello.o # 只编译不链接,生成目标文件 g++ hello.o -o hello # 单独链接为什么编译型语言快?
- 翻译只在构建期发生一次:最终交付给 CPU 的是一份已经"翻译好"的机器码,运行时没有额外的翻译开销。
- 机器码直接可执行:CPU 从内存中取指、解码、执行(对应仓库 程序运行流程 中描述的 Von Neumann 架构——CPU 执行存储在内存中的指令),无需中间层的解释开销。
- 代价是平台绑定:机器码与特定 CPU 架构绑定,跨平台需要重新编译。
字节码型语言:Java 的"编译一次,处处运行"
核心流程:源码先编译成字节码 → JVM 解释/执行字节码 → 必要时 JIT 编译成机器码。
关联文档指出:
"A bytecode language like Java, compiles the source code into bytecode first, then the JVM executes the program. Sometimes JIT (Just-In-Time) compiler compiles the source code into machine code to speed up the execution."
代表性语言:Java、C#。
两级执行模型
源代码 (Hello.java) --javac--> 字节码 (Hello.class) --JVM--> 机器码(解释执行 或 JIT 编译)- 编译阶段:
javac将.java源码编译为平台无关的.class字节码文件。 - 运行阶段:JVM 加载字节码。早期 JVM 以解释方式逐条执行字节码;现代 JVM 会统计热点代码,触发JIT 编译器将其编译为当前平台(x86、ARM 等)的机器码,此后该段代码直接以机器码速度运行。
# 编译与运行 javac Hello.java # 生成 Hello.class(字节码) java Hello # JVM 加载并执行JIT 与 AOT 的取舍
| 策略 | 时机 | 优点 | 代价 |
|---|---|---|---|
| 纯解释执行 | 运行时 | 启动快、跨平台 | 每次执行都要翻译,速度慢 |
| JIT 编译 | 运行时 | 热点代码编译后接近原生速度 | 启动时需预热,占用编译时间 |
| AOT 编译(如 GraalVM Native Image) | 构建期 | 启动快、接近原生速度 | 失去部分动态能力,构建复杂 |
JIT 之所以能"既快又兼容",在于它只在运行时针对当前平台生成机器码,动态优化(如方法内联、逃逸分析)也让 JVM 在某些场景下超过传统的静态编译产物。
字节码的"运行成本"不止于翻译
虚拟机模型下,运行时开销还包含自动内存管理。仓库的 垃圾回收机制 文档列出了 Java 的多种 GC 实现:
- Serial GC:适合单线程环境或小型应用;
- Parallel GC:又称吞吐量收集器,适合批量任务;
- CMS(Concurrent Mark-Sweep):低延迟取向,尽量缩短停顿;
- G1(Garbage-First):在吞吐量与延迟之间取得平衡,是长期以来的默认选择;
- ZGC:面向大堆、极小停顿的低延迟收集器。
这说明"字节码 + VM"模型在获得跨平台与托管内存便利的同时,需要持续投入 GC 调优、JIT 预热等工程手段来逼近原生性能。
解释型语言:Python 的"边翻译边执行"
核心流程:不预先编译,解释器在运行时直接读取并执行源码。
关联文档指出:
"Interpreted languages are not compiled. They are interpreted by the interpreter during runtime."
代表性语言:Python、JavaScript、Ruby。
运行时逐条执行
源代码 (hello.py) --解释器(Python)--> 逐条解析、执行(无独立机器码产物)以 Python 为例:
python hello.py # 解释器直接运行源码解释器在每个运行时刻都在做"读取一行 → 解析语法 → 执行"的工作。因此翻译成本被分摊到每一次运行中,这是解释型语言整体偏慢的根本原因。
Python 其实也有"字节码"这一步
严格来说,CPython 并不直接对源码文本求值:它会在内存中把源码编译成.pyc字节码(缓存于__pycache__/),再由 Python 虚拟机(PVM)逐条解释执行字节码。这既避免了重复解析语法的开销,也保留了运行时解释执行的灵活性:
# 查看当前源码对应的字节码指令 import dis dis.dis(compile('a = 1 + 2', '<test>', 'exec'))也就是说,Python 是**"编译到字节码 + 运行时解释字节码"**的混合形态——只是它的"编译器"工作在运行前一刻,产物不直接交付给 CPU。
解释型语言并非"永远慢"
仓库的 Javascript 执行原理 文档提供了一个典型反例:
"Modern engines such as V8 utilize Just-In-Time (JIT) technology to compile code into directly executable machine code."
- 经典解释器(如早期 Python 实现)逐条执行,速度受限;
- 现代引擎(V8、PyPy、Ruby 的 MJIT/YJIT 等)采用JIT 编译:先快速解释以收集运行信息(profile),再将热点代码编译为机器码并缓存复用,从而大幅缩短与编译型语言的差距。
因此,"编译型比解释型快"这一结论的适用范围正在被现代 JIT 技术持续压缩——解释型语言的优势在于开发效率、动态性与跨平台便携性,代价是运行时需要额外的翻译与优化开销。
三类执行模型对比速览
| 维度 | 编译型(C、C++、Go) | 字节码型(Java、C#) | 解释型(Python、JavaScript、Ruby) |
|---|---|---|---|
| 翻译产物 | 机器码 | 平台无关字节码 | 无独立机器码产物(或内存中字节码) |
| 翻译时机 | 构建期一次完成 | 编译期产字节码 + 运行时 JIT | 运行时逐条翻译 |
| 执行者 | CPU 直接执行 | JVM / CLR 执行,热点 JIT 编译 | 解释器逐条执行 |
| 跨平台 | 需按平台重新编译 | 字节码跨平台,需目标平台 VM | 天然跨平台,依赖解释器实现 |
| 启动速度 | 快(直接运行) | 需 VM 启动 + JIT 预热 | 快(无构建步骤) |
| 运行速度 | 通常最快 | 接近原生(热点编译后) | 通常较慢,JIT 后可改善 |
| 典型场景 | 系统软件、游戏引擎、高性能服务 | 大型企业应用、Android、后端服务 | 脚本、数据科学、Web 前端、快速原型 |
结语:选型不是单选题,而是权衡题
回到关联文档的结论——"编译型语言一般比解释型语言运行更快"——更准确的表述是:在不同执行模型下,翻译成本被分配到了不同的阶段:
- 追求极致运行性能与底层控制,C++ 这类编译型语言仍是首选;
- 追求跨平台与高吞吐服务,Java 这类"字节码 + JIT"模型在成熟工程化后可以非常接近原生性能;
- 追求开发效率与动态灵活性,Python、JavaScript 这类解释型语言用一部分运行时开销换来了更快的迭代速度,并且现代 JIT 引擎正在持续缩小这条性能鸿沟。
理解这三类模型的执行链路,是评估"为什么这段代码慢""为什么这个语言启动快""JVM 为什么要预热"等问题的前提,也是后续阅读 程序运行流程、Javascript 执行原理 与 垃圾回收机制 等深入主题的起点。
- 后端
- 文档
- 教程
【免费下载链接】system-design-101
Explain complex systems using visuals and simple terms. Help you prepare for system design interviews.
相关推荐
Arduino IDE编译与上传机制:从源码到硬件执行
Arduino IDE编译与上传机制:从源码到硬件执行 本文深入剖析了Arduino IDE的完整编译与上传机制,涵盖了从源码编译到硬件执行的整个流程。文章详细
开发工具IDE代码编辑器嵌入式OpenSpeedy编译教程:从源码到可执行文件的CMake配置详解
OpenSpeedy编译教程:从源码到可执行文件的CMake配置详解 OpenSpeedy是一个开源免费的游戏加速工具,通过CMake构建系统实现跨平台编译。本
桌面应用游戏开发Slang CPU Target 实战指南:从 Slang 到 C++ 的转译、Host-Callable 执行与 CPU ABI 全解析
Slang CPU Target 实战指南:从 Slang 到 C++ 的转译、Host Callable 执行与 CPU ABI 全解析 本文基于 Slang
编译器图形学编程语言
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考