JavaScript是解释型语言还是编译型语言?这是一个由来已久的争论。YDKJS(You Don't Know JS)的作者认为它是编译型语言。在研究了他为什么这么想之后,我得出了如标题所述的结论:JavaScript既是解释型语言,同时也是编译型语言。
现代JavaScript由各种引擎执行。让我们看看这些引擎如何执行JavaScript,以理解为什么我会得出这样的结论。
预备知识
JIT编译器
与传统的在执行前编译所有代码的编译方式不同,JIT(Just-In-Time)在程序执行过程中在需要的时刻编译代码并执行。它不是预先编译所有源代码,而是在运行时立即编译仅需要的部分,从而优化性能。
中间表示(Intermediate Representation, IR)
中间表示(IR)是编译器或解释器在处理源代码过程中内部使用的中间阶段代码或数据结构形式。在直接将源代码转换为机器码或执行之前,源代码被转换为更简单、更抽象的形式,使编译器或解释器能够更容易地分析和优化。
V8
Chrome的V8 JavaScript引擎由1个解释器和3个编译器组成。让我们简单看看它是如何工作的。
1. Ignition(字节码解释器)
原始的V8引擎通过在执行前对所有JavaScript代码进行JIT编译转换为本地机器码来提高速度。最初,它使用Baseline编译器进行快速编译。由于通过Baseline编译器编译的代码没有优化,它会通过V8的执行管道被高级编译器如TurboFan重新编译。
这种方法的问题是,即使是只执行一次的代码也会被编译,这在内存方面造成了很大的浪费。为了减轻这种开销,他们创建了一个名为Ignition的新JavaScript解释器来替代现有的Baseline编译器。
Ignition首先将JavaScript代码转换为简洁的字节码,然后由解释器执行这些字节码。
有一个关于这方面的演示视频,想深入了解的人可以参考!
2. Sparkplug(基线JIT)
虽然通过创建Ignition解释器改善了性能,但最终还是遇到了解释器特性的限制。因此,在2021年,他们创建了一个名为Sparkplug的编译器,它在Ignition和高级编译器TurboFan之间运作。
Sparkplug不是编译JavaScript,而是编译解释器已经创建的字节码。此外,它不使用中间表示(Intermediate Representation, IR),而是通过一次扫描字节码直接生成机器码。
Sparkplug不经过复杂的优化过程,只是简单地创建可快速执行的代码。更复杂的优化由TurboFan负责。
3. Maglev(最快优化JIT)
2023年,又出现了一个新的编译器。Ignition解释器执行速度快但性能低,Sparkplug几乎立即将字节码转换为机器码但优化水平有限,而TurboFan创建最高性能的优化代码但编译时间长。为了再次突破这些限制,他们创建了一个名为Maglev的编译器。
Maglev是Google的V8 JavaScript引擎中引入的最新JIT编译器,位于现有的Sparkplug和TurboFan之间。Maglev的目标是非常快速地生成"足够好"的优化代码。
4. TurboFan(高级优化JIT)
TurboFan是V8的顶级优化编译器。它执行最复杂和精细的优化,生成最高性能的机器码。
但TurboFan的缺点是编译时间长。因此,它只应用于非常频繁执行的"HOT 🔥"代码。V8通过分析跟踪哪些代码频繁执行,并只用TurboFan重新编译这些代码。
这样,V8通过4阶段执行管道为JavaScript代码特性选择最佳执行策略。
Webkit
Webkit同样由1个解释器和3个编译器组成。
1. LLInt(低级解释器)
LLInt是WebKit JavaScript引擎的第一个执行阶段。它将JavaScript代码转换为字节码,然后逐行读取并立即执行字节码。如果特定函数或代码多次执行("HOT 🔥"代码),它会尝试使用Baseline JIT进行编译。
2. Baseline JIT
当在LLInt中检测到代码多次执行时(函数6次,特定命令100次),它几乎直接将字节码转换为机器码。由于编译速度非常快,几乎没有执行延迟。它不进行优化。
3. DFG JIT(数据流图JIT)
当函数在Baseline JIT中被调用超过66次或特定命令执行超过1000次时,它会运作。它将字节码转换为中间表示(IR)。DFG JIT分析代码的数据流,执行各种编译器优化,如删除不必要的操作、推断变量类型和分配寄存器等。
4. FTL JIT(比光速更快JIT)
这是WebKit中使用的最高级编译器。它通过将JavaScript代码转换为本地代码来最大化执行速度。
SpiderMonkey
Firefox的SpiderMonkey也以类似的方式战略性地选择和使用解释器和各种JIT。
结论
我研究了三个最广泛使用的引擎。所有引擎都混合使用解释器方式和编译方式。它们首先使用解释器快速启动,然后根据需要逐步编译以提高性能。
我认为"JavaScript是解释型语言还是编译型语言"这个问题本身在现代JavaScript执行环境中并不恰当。将现代JavaScript描述为"混合使用解释器和编译器的语言"似乎更准确。
参考资料
- Interpreter (computing) - Wikipedia
- Firing up the Ignition interpreter · V8
- Maglev - V8's Fastest Optimizing JIT · V8
- Sparkplug — a non-optimizing JavaScript compiler · V8
- Introducing the WebKit FTL JIT
- Introducing the B3 JIT Compiler
- The Baseline Interpreter: a faster JS interpreter in Firefox 70 – Mozilla Hacks - the Web developer blog
- Warp: Improved JS performance in Firefox 83 – Mozilla Hacks - the Web developer blog