加入我们,或者,欢迎回来。
您需要 登录 才可以下载或查看,没有帐号?注册会员
x
本帖最后由 lisliz 于 2021-1-30 18:52 编辑
优化js代码的方法有很多,我这里想从JS引擎的层面上阐述下写出高效率代码的一些建议。
大家都知道目前最流行的浏览器是谷歌浏览器,他的内核程序叫做chromium,chromium被广泛应用在安卓的webview和各种H5游戏引擎,mz/mv打包和调试输出的游戏也含有它。其中chromium使用的js引擎叫做V8。
我先介绍下V8引擎对js对象属性访问的效率优化,然后再从此引出如何帮助V8引擎优化你的代码(意思就是写出高效率代码)
这是参考原文:https://v8.dev/blog/fast-properties
如下面一个简单的js对象。
var obj = {a:"1",b:"2"};
有a和b两个属性,在V8中,属性的存储分为三个方式:
方式1:对象内属性(访问速度极快)
方式2:隐藏类描述符所描述的属性(访问速度较快)
方式3:字典属性(访问速度慢)
让我来一一解释三个方式,方式1是直接存储与js对象本身上的属性,由于直接拿到js对象+固定的偏移地址就可完成读取,所以访问速度会非常快。
方式2,则需要通过“隐藏类”来取得存储的地址偏移量,从而完成读取。
关于“隐藏类”:由于V8内部的js对象本身分配的内存是固定而又有限的,方式1不可能适用于所有的js属性,所以V8会动态的维护一个叫做隐藏类的结构来存储js对象属性的信息。每当我们为js对象obj新增加属性时(例如obj.c=3;),V8都会动态的修改这个隐藏类。但是,对象总是在动态的添加或删除属性,维护隐藏类的开销会变得非常大。一旦当V8发现这样的问题,V8就会将方式2的所有属性转换成方式3来进行储存。
由于采用方式3存储的属性会阻止V8引擎对函数进行诸如内联缓存(下篇再讲这个)等优化,我们写代码无论如何也要避免让热点代码所访问的对象属性变成方式3。
如何知道自己的js代码中对象的属性是采用哪个方式存储的?
第一步:运行你的MV或MZ游戏,按F8,选中Memory选项卡。
第二步:在显示的界面里选中Heap snapshot单选按钮,最后按下最下面的Take snapshot按钮。
你会看到类似如下的界面:
在写有Class filter文本框键入Game_Map按回车,可以看到我们熟悉的$gameMap对象,把结果展开,找到map这一行,这就是刚才所说的隐藏类。展开隐藏类,里面有一个descriptors,里面存储了用方式2存储的属性,由于RM自身的代码本身是非常优秀的,随便找个系统对象,都是含有方式2存储的属性。
但是,我这里想举一个反例,大家看这样一个代码。
var testSlowObj = {}; for(let i = 0; i < 100; i++) { testSlowObj["s" + i] = i; };
var testSlowObj = {};
for(let i = 0; i < 100; i++) {
testSlowObj["s" + i] = i;
};
循环了100次,从外部给对象添加了100个属性,如下,对象在V8中看起来是这样的:隐藏类map为空,对象属性都在properties上,并且properties的类型是object properties,和刚才$gameMap的不一样,这说明V8已经将该对象的属性转换成方式3。
我们写代码最好避免从外界动态的为对象删除或者增加属性,你观察RM自带的代码就会发现,RM似乎从没有这样干过,都是自己在自己的prototype里声明自己的属性。
另外,鉴于隐藏类的维护原理。书写js脚本时,尽量在对象初始化时就创建所有的属性,不要动态的添加或删除属性,这样可以保证所有属性被添加进对象的顺序是一致的,减少V8维护隐藏类的开销。
最后,贴一个自己的横板动作银河城游戏在V8引擎(chrome浏览器)和Firefox浏览器下的运行效率对比(100只怪物同屏碰撞检测,物理模拟)。
同样一份代码,一个稳定60帧,一个只有15帧,V8引擎对JS的优化可见一斑,大家也不要对JS的效率有误解。在高度密集物理运算下的JS效率完全可以负担起动作游戏的严格要求。 |