【翻译】V8 JavaScript 引擎之延迟反序列化

简介: 原文链接 Lazy deserialization 延迟反序列化(Lazy deserialization) 延迟反序列化(Lazy deserialization)功能在 V8 6.4 中默认打开了,带来的好处就是平均每个 Chrome 的选项卡可以节约500KB左右的内存消耗。

原文链接 Lazy deserialization

延迟反序列化(Lazy deserialization)

延迟反序列化(Lazy deserialization)功能在 V8 6.4 中默认打开了,带来的好处就是平均每个 Chrome 的选项卡可以节约500KB左右的内存消耗。

如果你对这个话题感兴趣,可以继续往下读,否则就不用浪费时间了。

V8 快照介绍

首先,我们回顾一下 V8 是如何堆快照来加速 Isolate(基本上等同于Chrome里面的选项卡)创建的。google的郭扬有个很好的介绍文章自定义启动快照

  • JavaScript 规范包含许多内置功能,例如从数学函数到全功能的正则表达式引擎。每个新创建的 V8 上下文(V8 context)从开始就可以使用这些功能。为此,必须在创建上下文时在 V8 的堆上初始化这些功能,将全局对象(例如,浏览器中的窗口对象)和所有内置功能初始化。如果这一切都是从头开始,那么需要相当长的一段时间。
  • 幸好 V8 使用了一种方法来加速这个过程:跟微波炉加一个冰冻披萨来一顿快餐差不多,V8通过反序列化一个编译好的快照来直接初始化上下文。在一台普通的桌面PC上,这个方法可以将创建上下文的时间从 40ms 缩减 2ms;在一台普通的移动设备上,这个是数字是从 270ms 到 10ms。怎么样,相当可观吧,那么你可以继续往下看了。

回顾一下:快照对于启动性能非常重要,它被用来通过反序列化为每个 Isolate 创建 V8 堆的初始状态。因此,快照的大小决定了 V8 堆的最小大小,快照越大,反序列化后每个 Isolate 就会占用更多内存。

快照包含完全初始化一个新的 Isolate 所需的一切,包括语言常量(例如未定义的值),解释器使用的内部字节码处理程序,内置对象(例如String)以及安装在内置对象上的函数(例如,String.prototype.replace)以及它们的可执行代码对象。

startup-snapshot-size.png

从2016-01到2017-09启动快照大小(以字节为单位),x轴是V8的版本。

在过去的两年中,快照的规模几乎增加了三倍,从2016年初的大约 600KB 增加到今天的超过 1500KB。绝大多数增加来自序列化的 Code 对象,这些对象的数量都有所增加(例如,随着语言规范的发展和增长,最近增加了 JavaScript 语言);和大小(由新 CodeStubAssembler 管道生成的内置插件作为本机代码与更紧凑的字节代码或最小化的 JS 格式)。

这是个坏消息,因为我们希望尽可能降低内存消耗。

延迟反序列化

一个主要痛点是将快照的全部内容复制到每个 Isolate 中。无条件地加载所有内置函数会带来资源浪费,因为某些函数可能永远都不会使用到。

延迟反序列化的概念非常简单:只在被调用之前反序列化内置函数。

对一些最受欢迎的网站的快速调查表明,这种方法非常有吸引力:平均而言,只有30%的内置功能被使用,有些网站只使用16%。这看起来非常有前景,因为这些网站大部分都是重度 JS 用户,因此这些数据可以被看作是整个网络潜在的内存节省的(模糊)下限。

当我们开始研究这个方向时,事实证明延迟的反序列化与 V8 的体系结构很好地结合在一起,并且只有少数几个非侵入式的设计更改需要启动和运行:

  • 快照中的对象位置。在延迟反序列化之前,序列化快照中的对象顺序是不相关的,因为我们只能一次反序列化整个堆。延迟反序列化必须能够自行反序列化任何给定的内置函数,因此必须知道它在快照中的位置。
  • 单个对象的反序列化。 V8的快照最初是为完整的堆序列化而设计的,并且支持单个对象的反序列化需要处理一些怪癖,例如不连续的快照布局(一个对象的序列化数据可能会散布其他对象的数据)称为反向引用(可以直接引用之前在当前运行中反序列化的对象)。
  • 延迟的反序列化机制本身。在运行时,延迟反序列化处理程序必须能够:a)确定要反序列化的代码对象,b)执行实际的反序列化,以及c)将序列化的代码对象附加到所有相关函数。

我们对前两点的解决方案是为快照添加一个新的专用内置区域,该区域可能只包含序列化的代码对象。序列化按照定义良好的顺序进行,每个代码对象的起始偏移量保存在内置快照区域的专用部分中。反向引用和散布的对象数据都是不允许的。

内置函数的延迟反序列化DeserializeLazy built-in来处理,它在反序列化时安装在所有延迟内置函数上。在运行时调用时,它会对相关的 Code 对象进行反序列化,最后将其安装在 JSFunction (表示函数对象)和 SharedFunctionInfo(由相同函数文本创建的函数之间共享)上。每个内置函数最多只能反序列化一次。

除了内置函数之外,我们还为字节码处理程序实现了延迟反序列化。字节码处理程序是包含在 V8 的 Ignition 解释程序中执行每个字节码的逻辑的代码对象。与内置插件不同,它们既没有附加的 JSFunction 也没有 SharedFunctionInfo。相反,它们的代码对象直接存储在调度表中,解析器在调度到下一个字节码处理程序时将其索引。延迟反序列化与内置函数类似:DeserializeLazy 处理函数通过检查字节码数组,确定要反序列化的处理程序,反序列化代码对象,最后将反序列化的处理程序存储在调度表中。同样,每个处理程序最多只能反序列化一次。

验证结果

我们通过在 Android 设备上使用 Chrome 65 加载前 1000 个最受欢迎的网站(无论是否使用延迟反序列化)来评估内存节省。

memory-savings.png

平均而言,V8 的堆大小减少了 540KB,其中 25% 的受测站点节省了 620KB 以上,50%节省了 540KB 以上,75%节省了 420KB 以上。

运行时性能(以标准 JS 基准测量,如速度计,以及各种流行网站)均未受延迟反序列化的影响.

下一步

延迟反序列化确保每个 Isolate 只加载实际使用的内置代码对象。这已经是一个巨大的胜利,但我们相信可以更进一步,将每个 Isolate 的(内置相关)成本降低到零。

我们希望在今年晚些时候为您带来这方面的更新。敬请关注!

目录
相关文章
|
3月前
|
存储 JavaScript 前端开发
用 HTML + JavaScript DIY 渐进式延迟法定退休年龄测算器
用 HTML + JavaScript DIY 渐进式延迟法定退休年龄测算器
|
2月前
|
JavaScript 前端开发 Java
JS引擎V8
【10月更文挑战第9天】
29 0
|
4月前
|
Web App开发 JavaScript 前端开发
什么是JavaScript引擎
【8月更文挑战第14天】什么是JavaScript引擎
83 1
|
6月前
|
XML 缓存 JavaScript
一篇文章讲明白JS模板引擎之JST模板
一篇文章讲明白JS模板引擎之JST模板
56 2
|
7月前
|
JavaScript 前端开发 NoSQL
【MongoDB 专栏】MongoDB 的 JavaScript 引擎与脚本执行
【5月更文挑战第11天】MongoDB 的 JavaScript 引擎允许在服务器端直接执行脚本,提升效率并实现定制化操作。脚本环境提供独立但与数据库关联的运行空间,引擎负责脚本的解析、编译和执行。执行过程包括脚本提交、解析、编译和执行四个步骤。掌握脚本逻辑设计和 JavaScript 语言特性对于高效利用这一功能至关重要。例如,通过脚本可以计算商品总销售额,增强数据库操作的灵活性。
110 1
【MongoDB 专栏】MongoDB 的 JavaScript 引擎与脚本执行
|
6月前
|
缓存 自然语言处理 前端开发
深入剖析JavaScript引擎的工作原理
【6月更文挑战第3天】JavaScript引擎由解析器、解释器、优化器和垃圾回收器组成,它们协同完成代码的解析、编译和执行。解析器将源代码转为抽象语法树(AST),编译阶段进行作用域分析和变量提升。解释器执行AST,优化器在代码频繁执行时进行即时编译以提高性能。垃圾回收器自动回收不再使用的内存,防止泄漏。理解这些原理有助于优化代码和提升Web应用性能。
57 1
|
存储 JavaScript 前端开发
从 V8 优化看高效 JavaScript
从 V8 优化看高效 JavaScript
98 0
|
7月前
|
前端开发 JavaScript
Node.js 事件循环:定时任务、延迟任务和 I/O 事件的艺术
Node.js 事件循环:定时任务、延迟任务和 I/O 事件的艺术
Node.js 事件循环:定时任务、延迟任务和 I/O 事件的艺术
|
7月前
|
Web App开发 前端开发 JavaScript
探索 V8 引擎的内部:深入理解 JavaScript 执行的本质
探索 V8 引擎的内部:深入理解 JavaScript 执行的本质
探索 V8 引擎的内部:深入理解 JavaScript 执行的本质
|
7月前
|
JavaScript 前端开发 开发者
Vue.js深度解析:前端开发的生产力引擎
Vue.js深度解析:前端开发的生产力引擎
103 0