来源.
Based on that AST, the interpreter can start to do its thing and produce bytecode.
为了更快,将把字节码发送至优化编译器。基于 profilling data 产生高度优化的机器代码。如果某时刻发生错误,优化编译器将中断返回给解释器。
JavaScript引擎中的解释器/编译器管道
interpreter->bytecode->optimizing compiler(with profilling data)->optimized code
Chrome
V8 引擎基本一致。V8的解释器叫 Ignition ,负责生产和执行字节码。当它执行字节码的时候会在收集能在之后提升性能的 profilling data。当函数使用频繁,生产的字节码和 profilling data 会传入 TurboFan ,去生产高度优化的机器码。
Firefox
interpreter->bytecode
Baseline->somewhat optimized code
lonMonkey->optimized code
SpiderMonkey 引擎以及 SpiderNode,他们有两个优化编译器。Baseline compiler 生成一些优化代码,运行的时候获取 profilling data ,LonMonkey 生成高度优化代码。如果 the speculative optimization 失败,则回到 Baseline code 中。
Edge
interpreter->bytecode
SimpleJIT->somewhat optimized code
FullJIT->optimized code
微软 Edge 用的是 Chakra 以及 node-chakracore,跟两个优化编译器的步骤相似。解释器后进入 SimpleJIT 最后进入 FullJIT。
Safari
LLInt->bytecode
Baseline->somewhat optimized code
DFG(Data Flow Graph)->mostly optimized code
FTL(Faster Then Light)->optimized code
Apple Safari 用的是 JavaScriptCore ,用了三个不同的优化编译器。LLInt, the Low-Level Interpreter->the DFG (Data Flow Graph) compiler->the FTL (Faster Than Light) compiler。
总结
为什么各个引擎优化编译器不同?这是权衡。选择快速生成的低效的字节码还是花更多的时间运行高效的机器码,这是一个问题。有些引擎增加复杂度有更细颗粒度的去掌控这个问题。他们都有共同的结构:一个解析器、解释器/编译器管道。
JavaScript 对象模型
Object.getOwnPropertyDescriptor 获取对象描述符属性。Arrays are limited to 2³²−1 items in JavaScript。
Shapes
With that in mind, JavaScript engines can optimize object property access based on the object’s shape.1
2
3const object1 = { x: 1, y: 2 };
const object2 = { x: 3, y: 4 };
// `object1` and `object2` have the same shape.
为了节省内存,一份对象由 Shape 和 JSObject 组成。 Shape 由对象属性名及偏移量组成。JSObject 持有对象属性值。当有很多 Shape 一致的对象,JSObject 对应同一份 Shape。这样的好处很明显,不管有多少份对象,只要他们的 Shape 一样,那么我们只需要储存 Shape 一次。
在不同引擎里的叫法
- Academic papers call them Hidden Classes (confusing w.r.t. JavaScript classes)
- V8 calls them Maps (confusing w.r.t. JavaScript Maps)
- Chakra calls them Types (confusing w.r.t. JavaScript’s dynamic types and typeof)
- JavaScriptCore calls them Structures
- SpiderMonkey calls them Shapes
Transition chains and trees
Shape 继承空的 Shape ,当你不停在同父 Shape 进行修改时,Shape 的变化是 transition chain,但有分叉情况出现时,Shape 的变化是 transition tree。
This optimization shortens the transition chains and makes it more efficient to construct objects from literals.
尽可能缩短 transition chains。为了加快搜索属性, JavaScript 引擎添加了一个 ShapeTable 数据结构。此 ShapeTable 是一个字典,将属性键映射到引入给定属性的相应形状。
如果都启用字典查询了,我还担心什么? Shape 启用了 Inline Caches。
Inline Caches (ICs)
Shape 也借鉴了 ICs 的想法。它减少了很多昂贵开销的查找,使得寻找对象属性更快。
本质上就是 IC 会储存一次查找的值和偏移,如果下一次是同样的 Shape ,可以命中缓存,只需加载记忆偏移量的值则返回结果。
Storing arrays efficiently
数组里的索引,索引里储存的值都是可写可遍历的,所以不会有多份属性描述符。
如果你用 Object.defineProperty 定义数组的某值且改变属性描述符,将会把数组进入低效模式。
总结
- 始终以相同的方式初始化对象,以确保它们不会走向不同的 shape 方向。
- 不要混淆数组元素的属性特性(property attributes),以确保可以高效地存储和操作它们。
优化层级与执行效率的取舍
基于成本的考虑(内存与时间), Hot 的代码会进入优化编译器。