概述
转自 http://rednaxelafx.iteye.com/
现代JavaScript引擎都有哪些特征呢?跟以前的JavaScript引擎有怎样的差别,为什么变快了那么多?这里简单写下我的理解吧。
有很多同学可能会想从JavaScript引擎的源码着手一探究竟。这里也顺便介绍一下JavaScript引擎大致的组成部分与工作流程。了解这其中涉及的各种术语都是什么意思的话,读源码就能事半功倍,很多时候光看文件名就足以定位到自己关心的那部分实现。 <- TODO
早期JavaScript引擎的实现普遍跟同时代的其它脚本语言一样,比较“偷懒”。反正是“脚本语言”,当时的JavaScript脚本通常只包含很简单的逻辑,只运行很短时间就完事。没啥性能压力,得不到足够的重视与开发资源,性能自然是好不到哪里去,却也足以满足当时的需求。
非常早期的“Mocha”引擎实现得确实非常偷懒。字节码解释器、引用计数方式的自动内存管理、fat discriminated union形式的值表现形式。现在随便找本教写玩具语言实现的书上或许就会这么教…但只能用来写玩具 犀牛书第4版写了点JavaScript与引用计数的历史。
到1996年,Brendan Eich新写的SpiderMonkey已经改为使用mark-and-sweep GC、tagged value。
于是其实早期的两个主要的JavaScript引擎实现,Mozilla SpiderMonkey和Microsoft JScript其实都一直在用mark-and-sweep GC。也没啥别的主流JavaScript引擎用过引用计数方式来实现自动内存管理的。这点别被忽悠了。在叫得出名字的JavaScript引擎里只有quad-wheel(没听说过么?不奇怪,非主流嘛)是用引用计数方式实现自动内存管理的。
(老版本IE里JScript虽说是有因为循环引用而导致内存泄漏的问题,但那不是因为JScript自身用引用计数。问题出在JScript与DOM交互的边界上:IE的DOM节点(及其它host对象)是COM对象,而COM对象自身是引用计数的。这导致JScript与DOM交互时有可能被连累引发循环引用->内存泄漏的问题。IE9/Chakra里已经通过把DOM对象变成由JavaScript一侧来管理解决了这个问题。)
几种较老的JavaScript引擎的特征:
SpiderMonkey | JScript | KJS | |
实现语言 | C | C++ | C++ |
执行模式 | 解释执行 | 解释执行 | 解释执行 |
解释器 | 字节码解释器:基于栈的字节码 | 字节码解释器:基于栈的字节码 | 树遍历解释器 |
动态编译器 | 无 | 无 | 无 |
自动内存管理 | mark-and-sweep | mark-and-sweep | mark-and-sweep |
对象布局 | ? | 基本上是HashTable | ? |
针对密集数组的优化 | ? | 无 (JScript < 5.7);有(JScript 5.8) | ? |
Inline-cache | ? | ? | ? |
值表现形式 | tagged-value | 堆对象 | 堆对象 |
Function.prototype.toString() | 从字节码反编译 | ? | ? |
(几个术语:
树遍历解释器:tree-walking interpreter。遍历抽象语法树来解释执行的解释器。
对象布局: object representation 或者 object layout。指在堆上分配的JavaScript对象的在内存中的布局。
值表现形式: value representation。注意跟“对象布局”说的不是一件事。这个指的是原始类型数据、指向堆上分配的对象的指针之类的值的表现形式。对某些JavaScript引擎来说这是指“JSValue”背后在内存中的表现形式。
TODO 加上对parser的描述
SpiderMonkey | KJS | JavaScriptCore | V8 | Managed JScript |
手写纯递归下降式 | bison生成LALR(1) | bison生成的LALR(1) | 手写的递归下降+运算符优先级混合式 | 手写的纯运算符优先级式 |
早期JavaScript引擎得到的投入实在不足,而当时的Java虚拟机(JVM)却得到了大量资源实现各种优化,包括JIT编译器之类。这使得用Java写的Rhino一度能比用C写的SpiderMonkey跑得还快,因为Rhino得益于JVM里优秀的JIT编译器和GC,而SpiderMonkey还在用简易的解释器和GC。
这个阶段中,JavaScript对象的布局或者说表现方式通常可以叫做“property bag”,本质上就跟hashmap一样。
在Google推出V8之后,业界受到巨大冲击。V8的性能远高于当时所有其它JavaScript引擎,可以有效支撑起当时兴起的大量使用JavaScript的Web应用。
各大JavaScript引擎的实现者都坐不住了,像打了鸡血似的使劲优化优化再优化。先是把已在其它HLLVM上得到充分验证的优化技术引入到JavaScript引擎中,然后再针对JavaScript语言的特点做专项优化。
现在(2013-04)几种主流的JavaScript引擎的特征:
V8 | SpiderMonkey | Chakra | Nitro | Nashorn | |
实现语言 | C++/汇编 | C++ | C++ | C++/汇编 | Java |
执行模式 | 纯编译: 两层编译 | 解释/编译混合式: 3层执行模式 | 解释/编译混合: 2层执行模式,后台编译 | 解释/编译混合: 3层执行模式 | 纯编译 |
解释器 | 无 | 字节码解释器 | 字节码解释器:基于寄存器的字节码 | 字节码解释器 LLInt:基于寄存器的字节码 | 无 |
动态编译器 | 初级编译器 + 优化编译器 | 初级编译器 Baseline + 优化编译器 IonMonkey | 有 | 初级编译器 method JIT + 优化编译器 DFG JIT | 有 |
自动内存管理 | 分代式GC: 初生代: copying收集器; 年老代: 增量式mark-and-sweep, 可选compact | 分代式GC | 分代式GC: 初生代: copying收集; 年老代: 并发式mark-and-sweep | 分代式GC | 依赖于底层JVM的GC |
对象布局 | 紧凑+隐藏类 Map | 紧凑+隐藏类 Shape | 紧凑+隐藏类 | 紧凑+隐藏类 Structure | 紧凑+隐藏类 PropertyMap |
针对密集数组的优化 | 有 | 有 | 有 | 有 | 有 |
Inline-cache | MIC/PIC | PIC | PIC | PIC | MIC/PIC |
值表现形式 | tagged-pointer / IEEE 754 double / integer | pun-boxing | tagged-value | NaN-boxing | 堆对象 / integer |
正则表达式 | 编译 Irregexp | 编译 | 编译 | 编译 WREC | 混合 |
Function. prototype. toString() | 保留源码原文 | (2012年7月前) 从字节码反编译; (761723后) 保留源码原文 | ? | ? | 保留源码原文 |
(几个缩写:
copying GC: 也叫 scavenger。
MIC: monomorphic inline-cache
PIC: polymorphic inline-cache
pun-boxing: Packed NaN unboxing)
SpiderMonkey和LuaJIT似乎都在用pun boxing
所以说这年头是个JavaScript引擎都得有JIT编译器了…没有都不好意思出来混。受到平台限制(例如iOS、Windows Phone)而无法实现JIT编译器的“第三方JavaScript引擎“只好哭了。
TODO
最后
以上就是伶俐期待为你收集整理的JavaScript引擎实现的全部内容,希望文章能够帮你解决JavaScript引擎实现所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复