【读书笔记】 JavaScript 引擎基础:shapes和Inline Caches

本文详细介绍了JavaScript引擎中的Shapes和InlineCaches概念,展示了它们如何通过数据结构优化提升性能,包括内存管理、属性访问加速和代码执行效率。同时,作者提供了高性能编程的建议,尽管业务开发中可能无法像引擎那样深度优化,但保持代码可读性和扩展性也是一种优化策略。
摘要由CSDN通过智能技术生成

这是这篇文章的翻译,帮助了解 JS 引擎基础部分内容。

https://mathiasbynens.be/notes/shapes-ics

这篇文章介绍了一些关于 JS 引擎的基本概念Shapes和 Inline Caches,这有助于你理解代码性能。

JavaScript 引擎管线

引擎将源码转化为 AST(抽象语法树),然后解释器(interpreter)就可以生成字节码。
为了提高执行效率,字节码会跟 概要数据(profiling data) 一起发送到优化编译器,优化编译器根据 概要数据 作出一些的假设,生成高度优化的机器码。
在这里插入图片描述

如果有些假设是不正确的,优化编译器会反优化并会推到解释器执行。

解释器、编译器管线

在这里插入图片描述

解释器能很快的生成为优化的字节码,优化编译器随后话费相对更多的时间生成高度优化的机器码。
在这里插入图片描述

在Chrome 和 Node.js 使用的 V8 引擎中,解释器叫 Lngition,生成字节码,收集概要数据。概要数据之后可以提高执行效率。
当 一个函数经常调用,TurboFan(V8中的优化编译器)字节码和概要数据就会会生成高度优化的机器码。

SpiderMonkey 是 Mozilla 的 Firefox 浏览器和 SpiderNode 的引擎,实现起来就有些不同。这里就略过了。
在这里插入图片描述

这是微软的引擎管线,也有些不同。
在这里插入图片描述

为什么这些引擎都比 V8 多几个优化编译器呢?这是因为虽然解释器能很快生成字节码,但执行效率不高,而优化编译器虽然生成得慢,但机器码效率很高,多几个不同程度的编译器可以更精细的进行优化,提高执行效率。

JavaScript 的对象模型

接着看一下引擎如何实现 JS 对象模型的,以及如何加速访问对应属性。
ESMAScript规范定义了所有对象都是字典结果,并定义了一些属性的参数

  • Writable能否被写
  • Enumerable 能否被枚举,比如在 for-in 中显示
  • Configurable 能否被删除
    在这里插入图片描述

可以使用Object.getOwnPropertyDescriptor API来查看属性
在这里插入图片描述

对于数组,可以理解为一种特殊的对象。数组对下表有特殊的处理,数组长度限制为 2 的 32 次方- 1,数组有长度属性。
在这里插入图片描述

当有新的元素加到数组,JS 会自动更新 length 属性的 value 值。

优化属性访问

这里先介绍 shapes的概念

const object1 = { x: 1, y: 2 };
const object2 = { x: 3, y: 4 };

object1和 object2 有一样的 shape,shape 可以翻译为结构、索引?担心翻译得不准确,还是不翻了。

JS 引擎可以优化对象的属性访问,看看是如何实现的。

先看看属性参数是如何存在内存里面的呢?我们要把他们存到对象里吗?如果我们预测到之后会有很多一样 shape 的对象,那我们重复存就会造成浪费。引擎会作出优化,把 shape 单独存储。
在这里插入图片描述

shape 存储了除了 value 外的值,还存多了一个 offset用来标记属性的位置。
所有的 JS 引擎都有用 shapes 来做优化,不过有不同的名字:

  • 学术论文里叫 Hidden Classes
  • V8里叫 Maps
  • Chakra (Edge 的引擎,查克拉?)里叫 Types
  • JavaScriptCore(WebKit,苹果的)里叫Structures
  • SpiderMonkey(Firefox)叫 shapes

shape 的链和树

当给对象增加属性时,shape 如何变化呢?

const object = {};
object.x = 5;
object.y = 6;

在这里插入图片描述

这里属性的添加顺序会影响 shape,也就是{x:4,y:5}和{y:5, x:4}用两个不同的 shape。
不过实际并不是如上图一样存储 shape的,而是如下图,有一个链的关系。当访问o.x属性时,会沿着链表去找 x 。
在这里插入图片描述

除了会有链表关系,还可能有树的关系。比如这个例子:

const object1 = {};
object1.x = 5;
const object2 = {};
object2.y = 6;

在这里插入图片描述

shape 也不一定从空开始,比如这个例子

const object1 = {};
object1.x = 5;
const object2 = { x: 6 };

在这里插入图片描述

object2初始化时就有 x 属性,对应的 shape 也有直接有 x,而不是从空的 shape 开始构造链表,这样相对更高效。

那如果像这样的代码

const point = {};
point.x = 4;
point.y = 5;
point.z = 6;

就会有三个 shape?然后访问某个属性时,去获取这个属性的参数就得顺着这个链表去遍历?在这里插入图片描述

那读取某个属性的参数的时间复杂度就去到 O(n)了,JS 引擎为了优化这个时间,加多了一个叫 ShapeTable 的数据结构,其实就是一个map,做了属性名到 shape 的映射。
在这里插入图片描述

Inline Caches(ICs)

shapes 除了可以节省内存空间,还有一个作用就是帮助实现 Inline Caches,这是 JS 快的原因。
举个例子

function getX(o) {
	return o.x;
}

这段代码是从对象 o 里读取属性 x
用 JSC(javascript complie?) 生成字节码后是这样的 在这里插入图片描述

第一句意思是get_by_id 命令从第一个参数 arg1 里读取属性 x,并存储结果到 loc0。
第二句是返回 loc0.
JSC在 get_by_id 里有两个内联缓存,就是红色的那两个 N/A
在这里插入图片描述

当执行一次后,第一个缓存会写为 shape (的内存地址?),第二个会写入属性的偏移量 0。
在这里插入图片描述

当频繁调用 getX 方法时,如果发现 shape 没变,会直接读取到 offset 为0,实际中,如果 JS 引擎看到 shape 在内联缓存记录过就能直接用了,不需要再去查。

高效存储数组

考虑有如下数组

const array = [
	'#jsconfeu',
];

一般来说 JS引擎不会存储数组元素的参数,因为他们一般都是 wirable 、 enumerable 、 configurable 的。
在这里插入图片描述

但开发者也是有可能会改的,比如

// Please don’t ever do this!
const array = Object.defineProperty(
	[],
	'0',
	{
		value: 'Oh noes!!1',
		writable: false,
		enumerable: false,
		configurable: false,
	}
);

那 JS 引擎就会用 map 来存储下表和属性参数
在这里插入图片描述

即使数组只有一个元素有非默认的参数,整个数组的存储也会用这种更低效率的方法,逐个元素去查。因为应该避免在数组下标使用Object.defineProperty方法。

建议

这篇文章是2018年写的,这是当时他给出的高性能写法建议:

  • 用同样的方法初始化你的对象,这样就不会有不同的 shapes
  • 不要改变数组元素的参数,这样他们能高效存储和检索

从 Shapes 和Inline Caches 这两个点可以看出 JS引擎为了提高 JS执行效率做在数据结构、缓存上做了不少优化。

在日常的业务开发中,我们好像也只是用到了这些功能,比如数据库对频繁读的数据做了缓存加快查询速度,比如对象用到了一样的数据和方法就把他抽象为类。可能是一种遗憾,没有机会像 JS 引擎一样能深入的研究、优化。

但业务开发也能对复杂的业务系统保持持续迭代、重构,保持代码可读性、可扩展性,这可能从另一个纬度来看,也是一种优化吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值