写点什么

JS 引擎中的 Inline Cache 技术内幕,你知道多少?

  • 2020-12-29
  • 本文字数:3094 字

    阅读完需:约 10 分钟

JS 引擎中的 Inline Cache 技术内幕,你知道多少?

JavaScript 以简单易用而著称,NodeJS 的出现使 JavaScript 的影响进一步扩大。JavaScript 是动态类型的语言,动态类型为应用开发者带来了便利,但也为 JavaScript 运行时的性能带来了负担,例如类型的不断变化可能会导致基于类型的某些优化失效。为了解决 JavaScript 由于动态类型导致的运行性能受损问题,各大 JavaScript 引擎几乎都采用了 IC(Inline Cache)技术:即通过缓存上一次对象的类型信息来加速当前对象属性的读写访问。本文从引例入手,以 V8 JavaScript 引擎(主要由于 V8 既是 Chrome 浏览器的 JS 引擎,也是 node 的 JS 引擎)为基础,深入分析 Inline Cache 机制的基本原理。


引例


function Point(x,y) {this.x = x;this.y = y;}var p = new Point(0, 1);var q = new Point(2,3);var r = new Point(4,5);
复制代码


为了避免 API 调用不稳定因素的影响,通过修改 V8 源码,在内部插入时间戳的方式。在 3.2G 8 核机器上,分别测试三次调用 new Point(x,y)时执行 this.x=x 这个语句耗时,结果如下表所示。


执行代码this.x=x耗时统计
var p = new Point(0,1);4.11ns
var q = new Point(2,3);6.63ns
var r = new Point(4,5);0.65ns


从表中的结果可以看出,事实并非想象中的从第二次执行开始,速度就会变快,相反,第二次比第一次还要慢,到第三次的时候速度才会变快。本文后面会通过分析 V8 IC 机制来解释为什么第二次速度最慢,第三次执行速度又变快的原因。


问题分析


1. 对象的隐藏类(Hidden Class)


由于 JavaScript 对象没有类型信息,几乎所有 JS 引擎都采用隐藏类(Hidden Class/Shape/Map 等)来描述对象的布局信息,用以在虚拟机内部区分不同对象的类型,从而完成一些基于类型的优化。


V8 对 JavaScript 对象都使用 HeapObject 来描述和存储,每一种 JavaScript 对象都是 HeapObject 的子类,而每个 HeapObject 都用 Map 来描述对象的布局。对象的 Map 描述了对象的类型,即成员数目、成员名称、成员在内存中的位置信息等。


上述源码中对象 p、q、r 都是由同一个构造函数 Point 生成,因此他们具有同样的内存布局,可以采用同一个 Map 来描述。


2. 隐藏类变迁(Map Transition)


因为 JavaScript 是高度动态的程序设计语言,对象的成员可以被随意动态地添加、删除甚至修改类型。因此,对象的隐藏类在程序的运行过程中可能会发生变化,V8 内部把这种变化叫隐藏类变迁(Map Transition)。


以上文代码为例,当 Point function 被声明时,V8 就会给 Point 创建隐藏类 map0,由于暂时还没有属性,因此 map0 为空。当执行 this.x=x 时,V8 会创建第二个 Hidden Class map1,map1 是基于 map0,并且描述了属性 x 在内存中的位置,此时 this 对象的 Hidden Class 会通过 Map Transition 变为 map1。当执行 this.y=y 时,会重复前面的操作,一个新的 Hidden Class map2 会被创建,此时 this 对象的 Hidden Class 被更新为 map2。this 对象的 Map 从 map0 变为 map1,再变成 map2 的过程就叫做 Map Transition。图一描述了 Point 类对象创建过程中 Map Transition 的过程。


Map Transition示意图


3. 类型反馈向量(type feedback vector)


前面已经提到 IC 机制的原理是:对于某代码语句比如 this.x=x,比较上次执行到该语句时缓存的 Map 和对象当前的 Map 是否相同,如果相同则执行对应的 IC-Hit 代码,反之执行 IC-Miss 代码。那么 V8 是如何组织被缓存的 Map 和 IC-Hit 代码?以上文代码为例,V8 会在 Point 函数对象上添加一个名为 type_feedback_vector 的数组成员,对于该函数中的每处可能产生 IC 的代码,Point 对象中的 type_feedback_vector 会缓存上一次执行至该语句时对象的 Map 和对应的 IC-Hit 代码(在 V8 内部称为 IC-Hit Handler)。上文中的 Point 函数中有两处可能产生 IC 的语句,this.x=x 和 this.y=y。假设某次执行至 this.x=x 时,对象 this 的 Map 是 map0,执行至 this.y=y 时 this 的 Map 是 map1,那么 Point 对象的 type_feedback_vector 数据内容如下所示:


数组下标IC对应的源码缓存的Map和对应的IC-Hit Handler
0this.x=x<map0, ic-hit handler>
1this.y=y<map1, ic-hit handler>


简单来说,type_feedback_vector 缓存了 Map 和与之对应的 IC-Hit handler,这样 IC 相关的逻辑简化为只需要通过访问 type_eedback_vector 就可以判断是否 IC Hit 并执行对应的 IC-Hit Handler。


4. IC 状态机


为了描述 V8 中 IC 状态的变化情况,本节将以状态机的形式描述 V8 中最常见 IC 种类的状态变化情况。V8 中最常用 的 IC 分为五个状态,如图二所示。初始为 uninitialized 状态,当发生一次 IC-Miss 时会变为 pre-monomorphic 态,再次 IC-Miss 会进入 monomorphic 态,如果继续 IC-Miss,则会进入 polymorphic 状态。进入 polymorphic 之后如果继续 IC-Miss 3 次,则会进入 megamorphic 态,并最终稳定在 megamophic 态。


IC状态机


引例中代码会涉及到 IC 状态机的前三种状态。


以 Point 函数走红 this.x=x 语句为例,第一次执行时,由于 Point.type_feedback_vetor 为空,因此此时会发生 IC-Miss,并将该处 IC 状态从 uninitialized 设置为 pre-monomorphic,IC-Miss Handler 会分析出此时 this 对象的 Map 中不包含属性 x,因此会添加成员 x,接着会发生 Map Transition,即前文提到的 this 对象的隐藏类从 map0 变为 map1。由于考虑到大部分函数可能只会被调用一次,因此 V8 的策略是发生第一次 IC-Miss 时,并不会缓存此时的 map,也不会产生 IC-Hit handler;


第二次调用构造函数执行 this.x=x 时,由于 Point.type_feedback_vector 仍然为空,因此会发生第二次 IC-Miss,并将 IC 状态修改为 monomorphic,此次 IC-Miss Hanlder 除了发生 Map Transition 之外,还会编译生成 IC-Hit Handler,并将 map0 和 IC Hit Handler 缓存到 Point.type_feedback_vector 中。由于此次 IC-Miss Handler 需要编译 IC-Hit Handler 的操作比较耗时,因此第二次执行 this.x=x 是最慢的;


第三次调用构造函数中 this.x=x 时,发现 Point.type_feedback_vector 不为空,且此时缓存的 map0 与此时 this 对象的 Map 也是一致的,因此会直接调用 IC-Hit Handler 来添加成员 x 并进行 Map transition。由于此次无需对 map0 进行分析,也无需编译 IC-Hit Handler,因此此时执行效率比前两次都高。


至此,本节已经解释清楚为什么 V8 执行构造函数时,第二遍最慢而第三遍最快的原因。


5. Polymorphic 和 Megamorphic


function f(o) {  return o.x;}f({x:1}) //pre-monomorphicf({x:2}) //monomorphicf({x:3, y:1}) // polymorphic degree 2f({x:4, z:1}) // polymorphic degree 3f({x:5, a:1}) // polymorphic degree 4f({x:6, b:1}) // megamorphic
复制代码


上述代码描述了图二状态机中 polymorphic 态和 megamophic 态的两种情形。上面 3 中提到 type_feedback_vector 会缓存 Map 和 IC-Hit Handler,但是如果 IC 状态太多比如到达 megamorphic 态,此时 Map 和 IC-Hit Handler 便不会再缓存在 Point 对象的 feedback_vector 中,而是存储在固定大小的全局 hashtable 中,如果 IC 态多于 hashtable 的大小,则会对之前的缓存进行覆盖。通过上述分析,可以总结得出不同 IC 态的性能:


  • 如果每次都能在 monomorphic 态 IC-Hit,代码的运行速度是最快的;

  • 在 polymorphic 态 IC-Hit 时,需要对缓存进行线性查找;

  • Megamorphic 是性能最低的 IC-Hit,因为需要每次对 hashtable 进行查找,但是 megamorphic ic hit 性能仍然优于 IC-Miss;

  • IC-Miss 性能是最差的;


综合前文所述,仅从 Inline cache 的角度来分析,如果 JavaScript 开发者在应用开发时能让 IC 态保持在 monomorphic 或者 polymorphic,代码的性能是最好的。特别是对于一些比较注重应用冷启动性能的场景,减少启动过程中的 IC-Miss 会使启动时间大幅缩短。


参考文献




头图:Unsplash

作者:廖彬

原文JS引擎中的Inline Cache技术内幕,你知道多少?

来源:腾讯云中间件 - 微信公众号 [ID:gh_6ea1bc2dd5fd]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2020-12-29 23:352845

评论

发布
暂无评论
发现更多内容

手把手教你基于luatos的4G(LTE Cat.1)模组接入华为云物联网平台

华为云开发者联盟

物联网 华为云 华为云开发者联盟 企业号 3 月 PK 榜 4G

国家基础学科公共科学数据中心与和鲸科技共建数据社区

ModelWhale

数据 科学分析 社区 合作

【信创小知识】国产化和信创是一回事吗?怎么理解?

行云管家

信创 国产化

阿里三面被面试官狂问Redis,简历上再也不敢写"精通"了

Java 数据库 redis 缓存 面试

「 项目管理 」项目立项前需要思考的9个问题

小刘学编程

项目管理 pmp 项目经理

增强认证--MQTT 5.0新特性

EMQ映云科技

物联网 IoT mqtt 企业号 3 月 PK 榜 增强认证

买了等保安全设备就一定安全吗?就一定能抵御网络风险呢?

行云管家

网络安全 等保 等级保护

PCB焊盘设计应掌握哪些要素?

华秋电子

坚如磐石:TiDB 基于时间点的恢复(PiTR)特性优化之路丨6.5 新特性解析

TiDB 社区干货传送门

新版本/特性解读

课程作业及比赛任务,已支持 Notebook 内直接提交|ModelWhale 版本更新

ModelWhale

人工智能 机器学习 数据分析 canvas 模型管理

BSN-DDC基础网络详解(五):接入DDC网络(1)

BSN研习社

基于 Istio 的灰度发布架构方案实践之路

京东科技开发者

微服务 istio 灰度发布 企业号 3 月 PK 榜

通过Chaos-Mesh打造更稳定TiDB数据库高可用架构(一)

TiDB 社区干货传送门

实践案例 集群管理 管理与运维 扩/缩容 数据库架构设计

手把手教你改 sysbench 代码

TiDB 社区干货传送门

开发语言 管理与运维

又一个开源第一!飞桨联合百舸,Stable Diffusion推理速度遥遥领先

百度Geek说

人工智能 开源 PaddlePaddle 企业号 3 月 PK 榜

GitHub险崩盘,竟是因网易大牛「Redis应用与深度实践笔记」泄露

Java 数据库 redis 缓存 面试

软件测试/测试开发丨后端Web开发框架(Java)

测试人

软件测试 springboot 测试开发

通过Chaos-Mesh打造更稳定TiDB数据库高可用架构(二)

TiDB 社区干货传送门

实践案例 集群管理 管理与运维 故障排查/诊断 安装 & 部署

通过TiDB Operator为已有TiDB集群部署异构集群

TiDB 社区干货传送门

集群管理 管理与运维 故障排查/诊断 安装 & 部署 扩/缩容

MQTT 5.0连接属性

EMQ映云科技

物联网 IoT mqtt 企业号 3 月 PK 榜 连接属性

详解命令模式本质及其在高复杂调用中的实践案例

阿里技术

设计模式 命令模式

脚本调用工具:FastScripts 直装版

真大的脸盆

Mac 脚本 Mac 软件 Mac 系统

TIDB升级发生故障时,快速强行回退方案

TiDB 社区干货传送门

实践案例

对TiDB监控方式的一点点研究

TiDB 社区干货传送门

监控 TiDB 源码解读

TiDB 的事务和一致性校验工具 BANK

TiDB 社区干货传送门

实践案例 故障排查/诊断 数据库架构选型

Stable Diffusion原理详解

jarodyv

人工智能 机器学习 计算机视觉 Stable Diffusion 生成式AI

Spring源码分析-BeanFactoryPostProcessor

Java spring spring源码

Region is unavailable的排查总结

TiDB 社区干货传送门

管理与运维 故障排查/诊断 扩/缩容

物理机安装 TiKV 时 RAID 卡在线配置方式

TiDB 社区干货传送门

实践案例 集群管理 安装 & 部署

FinOps首次超越安全成为企业头等大事|云计算趋势报告

SEAL安全

云计算 云成本 FinOps 企业号 3 月 PK 榜

【征文大赛】TiDB 社区第二届征文大赛,一次性带走社区全部新周边,还有bose 降噪耳机、倍轻松按摩仪等你拿!

TiDB 社区干货传送门

JS 引擎中的 Inline Cache 技术内幕,你知道多少?_语言 & 开发_腾讯云中间件_InfoQ精选文章