“AI 技术+人才”如何成为企业增长新引擎?戳此了解>>> 了解详情
写点什么

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:352194

评论

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

Android岗高频面试题二集,看你能答出几题?(含答案

android 程序员 移动开发

Android开发——优化Glide加载超大gif图

android 程序员 移动开发

Android开发工程师:都想要进大厂,那么大公司面试的方向和面试官套路你都摸透了吗?(1)

android 程序员 移动开发

Android开发工程师:都想要进大厂,那么大公司面试的方向和面试官套路你都摸透了吗?

android 程序员 移动开发

Android开发没有一技之长就废了吗?

android 程序员 移动开发

Android刘海屏、水滴屏全面屏适配详解

android 程序员 移动开发

消息队列RocketMQ应对双十一流量洪峰的“六大武器”

阿里巴巴云原生

阿里云 RocketMQ 云原生 消息队列 流量

Android屏幕适配方案

android 程序员 移动开发

android各种提示Dialog 弹出框(1)

android 程序员 移动开发

android图片加载库Glide4使用教程(项目中如何快速将Glide3替换成Glide4)

android 程序员 移动开发

Android命令Monkey压力测试,详解

android 程序员 移动开发

Android开发3年,九月份面试12家大厂跳槽成功,我有一些面试经验想分享给你们

android 程序员 移动开发

Android体系化进阶学习图谱:我们究竟还要学习哪些Android知识?(某大厂内部资料

android 程序员 移动开发

Android修炼系列(十二),自定义一个超顺滑的回弹RecyclerView

android 程序员 移动开发

Android内存泄漏问题

android 程序员 移动开发

Android厂商推送冲突了。。。

android 程序员 移动开发

Android应用进程间通信之Messenger信使使用及源码浅析

android 程序员 移动开发

Android开发3年,4个月面试,终于拿到小米、京东

android 程序员 移动开发

基于 Istio 的全链路灰度方案探索和实践

阿里巴巴云原生

阿里云 云原生 istio 灰度 全链路

Android刘海屏、水滴屏全面屏适配方案

android 程序员 移动开发

android各种提示Dialog 弹出框

android 程序员 移动开发

基于 OpenYurt & EdgeX Foundry 的云边端一体化解决方案

阿里巴巴云原生

云原生 边缘计算 openyurt EdgeX Foundry

Android开发7年,疫情期间八家大厂高工的面试经历和真题整理

android 程序员 移动开发

Android开发中如何实现折线图的方法 (1)

android 程序员 移动开发

Android备忘录《内存泄漏》

android 程序员 移动开发

Android属性动画——ObjectAnimator类及浮动菜单的实现

android 程序员 移动开发

Android平台HTTPS抓包全方案

android 程序员 移动开发

Android开发 申请Mob的SMSSDK的短信验证码功能中获取MD5签名(更新中)

android 程序员 移动开发

Android使用ViewPager实现图片轮播系列之四:手动滑动 + 左右箭头 + 删除数据

android 程序员 移动开发

Android动画之补间动画

android 程序员 移动开发

Android常见问题及开发经验总结(一)

android 程序员 移动开发

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