阿八博客
  • 100000+

    文章

  • 23

    评论

  • 20

    友链

  • 最近新加了很多技术文章,大家多来逛逛吧~~~~
  • 喜欢这个网站的朋友可以加一下QQ群,我们一起交流技术。

【译】JavaScript engine fundamentals: Shapes and Inline Caches

欢迎来到阿八个人博客网站。本 阿八个人博客 网站提供最新的站长新闻,各种互联网资讯。 喜欢本站的朋友可以收藏本站,或者加QQ:我们大家一起来交流技术! URL链接:https://www.abboke.com/jsh/2019/1010/116434.html

JavaScript 引擎原理:外形与内联缓存

前言

前往 ➡️ 我的博客

本文是根据自己的理解翻译而来,如有疑惑可查看原文 JavaScript engine fundamentals: Shapes and Inline Caches

本次暂定翻译三篇文章:

JavaScript engine fundamentals: Shapes and Inline Caches(Published 14th June 2018)JavaScript engine fundamentals: optimizing prototypes(Published 16th August 2018)The story of a V8 performance cliff in React(Published 28 August 2019)

JavaScript 引擎工作流

一切从你写的 JavaScript 代码开始
JavaScript 引擎会解析源码并将其转换成抽象语法树(AST)
基于 AST,解释器(interpreter)会进一步地生成字节码

上面的流程和 V8 在浏览器和 Node 环境下的工作流程及其相似:

SpiderMonkey,Mozilla 的 JavaScript 引擎,拥有两个优化编译器,Baseline 和 IonMonkey
解释器将转换后的代码传给 Baseline 编译器,Baseline 编译器会将其加工成部分优化的代码
再加上收集到的分析数据,IonMonkey 编译器就可以生成高度优化的代码
如果基于假设的优化不成立,IonMonkey 会将代码会滚到 Baseline 部分

JavaScriptCore(JSC),Apple 的 JavaScript 引擎,更是发挥到了极致,使用了三个不同的优化编译器,Baseline、DFG 和 FTL
低级解释器(LLInt)将转换后的代码传给 Baseline 编译器,经其加工后传给 DFG(Data Flow Graph) 编译器,进一步加工后,传给 FTL(Faster Than Light) 编译器

为什么有些引擎的优化编译器会比其他引擎的多?这完全是取舍问题
解释器可以很快地生成字节码,但是字节码的效率不高
优化编译器虽然会花更长的时间,但是生成的机器码更为高效
是更快地去执行代码,还是花些时间去执行更优的代码,这都是需要考虑的问题
有些引擎添加多种不同特点(省时或高效)的优化编译器,虽然这会变得更加复杂,但却可以对以上的取舍有着更细粒度地控制
还有一点需要考虑的是,内存的使用

以上只是强调了不同 JavaScript 引擎的解析器/编译器的区别
抛开这些不谈,从更高的层面来看,所有的 JavaScript 引擎有着相同的架构:一个解析器和一些解释器/编译器

JavaScript 对象模型

再来看看,在某些具体实现上,JavaScript 引擎之间还有哪些相同之处

例如,JavaScript 引擎是如何实现 JavaScript 对象模型的?它们又是如何提升对象属性访问速度的?事实证明,所有主流的引擎在这点实现上都非常得相似

ECMAScript 规范把所有的对象定义为词典,将字符串键映射到属性特性(property attributes)

ok,这是 JavaScript 如何定义对象的
那么,数组呢?

你可以认为数组是一个特殊的对象
一个不同点是,数组会对数组索引特殊处理
数组索引是 JavaScript 规范中的一个特殊术语
数组索引是某个范围内的任何有效索引,即在 0 ~ 2³²−2 范围内的任何一个整数

另一个不同点是,数组还有一个 length 属性

另一个属性是 'length' 属性,该属性不可枚举不可配置

一旦数组添加一个元素,JavaScript 会自动更新 'length'属性上的 [[Value]]

外形(Shapes)

在 JavaScript 程序中,有相同键的对象很多,它们都有着相同的 Shape

考虑到这一点,JavaScript 引擎可以基于对象的 Shape 来优化对象属性的访问速度

我们假设一个对象有 x、y 属性,且用着字典这种数据结构:它包含字符串表示的键,并且键指向各自的属性特性(property attributes)

Shape 包含所有的属性名称和属性特性,除了 [[Value]]
不过,Shape 包含了 [[Value]]JSObject 上的偏移量,因此 JavaScript 引擎知道去哪里找到相应的值
每个拥有相同 ShapeJSObject 都指向同一个 Shape 实例
现在,每个 JSObject 只需存储对象的值即可

在 JavaScript 引擎中,这种 Shapes 结构称之为过渡链(transition chains)
如下:

如果你在 JavaScript 代码中写了 o.x,JavaScript 引擎会沿着过渡链查找属性 'x',直到发现引入 'x'Shape

但是,如果没法创建过渡链呢?例如,给两个空对象添加不同的属性

在这里,我们创建了一个空对象 a 并给它添加了属性 'x'
最终得到以一个包含单个值的 JSObject和两种 Shape(空的 Shape 和仅有属性 'x'Shape

第二个例子也是以一个空对象 b 开始,但是添加的是属性 'y'
最终得到两条 Shape 链和三个 Shape

这是否意味着总是以空 Shape 开头呢?不一定

引擎对已经存在属性的对象字面量做了优化
来看两个例子,一个是从空的对象开始添加属性 'x',一个是已经存在属性 'x' 的对象字面量

这个包含属性 'x' 的对象,以包含 'x'Shape 开头,省去了空 Shape 这个步骤
至少 V8 和 SpiderMonkey 是这么做的
这种优化缩短了过渡链,使得创建对象更加高效

Benedikt 的文章 surprising polymorphism in React applications 讨论了这些微妙之处是如何影响到实际性能的

这有一个拥有属性 'x'、'y''z'` 的三维点对象的例子

如果这种操作很频繁,就会显得很慢,尤其是一个对象有很多属性时
检索到需要的属性所花时间是 O(n),即线性的
为了提高检索速度,JavaScript 引擎加入了 ShapeTable 数据结构
ShapeTable是个字典,它将属性和引入该属性的 Shape 关联起来

如果我们在 JSC(JavaScriptCore) 中运行这个函数,它会生成以下的字节码:

现在给函数 getX 传入对象 { x: 'a' }
如我们所知,这个对象有一个包含属性 xShape,这个 Shape 存储了属性 x 的偏移量和特性
当我们第一次执行函数时,get_by_id 指令会查找属性 x并检索到值被存储在偏移量为 0 位置

在下次函数执行时,内联缓存会对比 Shape,如果与之前的 Shape 相同,就只需要通过缓存的偏移量加载值
具体来说,如果 JavaScript 引擎发现对象的 Shape 和之前记录的 Shape 一样,那么它就再也不需要去查找属性信息了 —— 属性信息的查找就可以完全跳过
相比每次都去查找属性信息,这样的操作会显著地提升速度

高效存储数组(Storing arrays efficiently)

对于数组,使用数组索引作为数组的属性是很常见的,属性对应的值称之为数组元素
为每个数组的每个数组元素存储属性特性是一种铺张浪费的行为
在 JavaScript 引擎中,数组的索引属性默认是可读、可枚举和可配置的,且数组元素是与命名属性分开存储的

思考以下这个数组:

这个之前见过的很相似…… 但是数组元素的值存在哪呢?

上面的这个代码片段是给对象属性 '0' 的特性设置成非默认值

像这种情况,JavaScript 引擎会将整个元素备份存储表示为一个字典,把数组索引和属性特性关联起来

相关文章

暂住......别动,不想说点什么吗?
  • 全部评论(0
    还没有评论,快来抢沙发吧!