前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JS引擎中的Inline Cache技术内幕,你知道多少?

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

作者头像
腾讯云中间件团队
发布2021-03-24 15:15:15
7190
发布2021-03-24 15:15:15
举报

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

01

引例

代码语言:javascript
复制
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

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

0

this.x=x

<map0, ic-hit handler>

1

this.y=y

<map1, ic-hit handler>

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

代码语言:javascript
复制
function f(o) {

  return o.x;
}

f({x:1}) //pre-monomorphic

f({x:2}) //monomorphic

f({x:3, y:1}) // polymorphic degree 2

f({x:4, z:1}) // polymorphic degree 3

f({x:5, a:1}) // polymorphic degree 4

f({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会使启动时间大幅缩短。

【参考文献】

  • https://v8.dev/docs
  • https://mathiasbynens.be/notes/shapes-ics
  • https://richardartoul.github.io/jekyll/update/2015/04/26/hidden-classes.html
  • https://mrale.ph/blog/2012/06/03/explaining-js-vms-in-js-inline-caches.html
  • https://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html

作者介绍

廖彬, 腾讯云中间件高级语言虚拟机核心研发,曾是阿里巴巴车联网系统AliOS JavaScript虚拟机核心研发人员,有多年高级语言虚拟机开发经验,熟悉各类GC算法、JavaScript Runtime等领域。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯云中间件 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
消息队列 TDMQ
消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档