主题包括:
代码执行基本原理
基准测试
内存优化
图形渲染性能
减少CPU使用量
其他优化策略
第1节:代码执行基本原理
LayaAir引擎支持AS3、TypeScript、JavaScript三种语言开发,然而无论是采用哪种开发语言,最终执行的都是JavaScript代码。所有看到的画面都是通过引擎绘制出来的,更新频率取决于开发者指定的FPS,例如指定帧频率为60FPS,则运行时每个帧的执行时间为六十分之一秒,所以帧速越高,视觉上感觉越流畅,60帧是满帧。
由于实际运行环境是在浏览器中,因此性能还取决于JavaScript解释器的效率,指定的FPS帧速在低性能解释器中可能不会达到,所以这部分不是开发者能够决定的,开发者能作的是尽可能通过优化,在低端设备或低性能浏览器中,提升FPS帧速。
LayaAir引擎在每帧都会重绘,在性能优化时,除了关注每帧执行逻辑代码带来的CPU消耗,还需要注意每帧调用绘图指令的数量以及GPU的纹理提交次数。
第2节:基准测试
LayaAir引擎内置的性能统计工具可用于基准测试,实时检测当前性能。开发者可以使用laya.utils.Stat类,通过Stat.show() 显示统计面板。具体编写代码如下例所示:
Stat.show(0,0); //AS3的面板调用写法 Laya.Stat.show(0,0); //TS与JS的面板调用写法
Canvas渲染的统计信息:
WebGL渲染的统计信息:
统计参数的意义:
FPS:
每秒呈现的帧数(数字越高越好)。
使用canvas渲染时,描述字段显示为FPS(Canvas),使用WebGL渲染时,描述字段显示为FPS(WebGL)。
Sprite:
渲染节点数量(数字越低越好)。
Sprite统计所有渲染节点(包括容器),这个数字的大小会影响引擎节点遍历,数据组织和渲染的次数。
DrawCall:
DrawCall在canvas和WebGL渲染下代表不同的意义(越少越好)。
Canvas下表示每帧的绘制次数,包括图片、文字、矢量图。尽量限制在100之下。
WebGL下表示渲染提交批次,每次准备数据并通知GPU渲染绘制的过程称为1次DrawCall,在每1次DrawCall中除了在通知GPU的渲染上比较耗时之外,切换材质与shader也是非常耗时的操作。 DrawCall的次数是决定性能的重要指标,尽量限制在100之下。
Canvas:
三个数值 —— 每帧重绘的画布数量 / 缓存类型为“normal”类型的画布数量 / 缓存类型为“bitmap”类型的画布数量”。
CurMem:仅限WebGL渲染,表示内存与显存占用(越低越好)。
Shader:仅限WebGL渲染,表示每帧Shader提交次数。
无论是Canvas模式还是WebGL模式,我们都需要重点关注DrawCall,Sprite,Canvas这三个参数,然后针对性地进行优化。(参见“图形渲染性能”)
第3节:内存优化
对象池
对象池,涉及到不断重复使用对象。在初始化应用程序期间创建一定数量的对象并将其存储在一个池中。对一个对象完成操作后,将该对象放回到池中,在需要新对象时可以对其进行检索。
由于实例化对象成本很高,使用对象池重用对象可减少实例化对象的需求。还可以减少垃圾回收器运行的机会,从而提高程序的运行速度。
以下代码演示使用
Laya.utils.Pool:
ar SPRITE_SIGN = 'spriteSign'; var sprites = []; function initialize() { for (var i = 0; i < 1000; i++) { var sp = Pool.getItemByClass(SPRITE_SIGN, Sprite) sprites.push(sp); Laya.stage.addChild(sp); } } initialize();
在initialize中创建大小为1000的对象池。
以下代码在当单击鼠标时,将删除显示列表中的所有显示对象,并在以后的其他任务中重复使用这些对象:
Laya.stage.on("click", this, function() { var sp; for(var i = 0, len = sprites.length; i < len; i++) { sp = sprites.pop(); Pool.recover(SPRITE_SIGN, sp); Laya.stage.removeChild(sp); } });
调用Pool.recover后,指定的对象会被回收至池内。
使用Handler.create
在开发过程中,会经常使用Handler来完成异步回调。Handler.create使用了内置对象池管理,因此在使用Handler对象时应使用Handler.create来创建回调处理器。以下代码使用Handler.create创建加载的回调处理器:
Laya.loader.load(urls, Handler.create(this, onAssetLoaded));
在上面的代码中,回调被执行后Handler将会被对象池收回。此时,考虑如下代码会发生什么事:
Laya.loader.load(urls, Handler.create(this, onAssetLoaded), Handler.create(this, onLoading));
在上面的代码中,使用Handler.create返回的处理器处理progress事件。此时的回调执行一次之后就被对象池回收,于是progress事件只触发了一次,此时需要将四个名为once的参数设置为false:
Laya.loader.load(urls, Handler.create(this, onAssetLoaded), Handler.create(this, onLoading, null, false));
释放内存
JavaScript运行时无法启动垃圾回收器。要确保一个对象能够被回收,请删除对该对象的所有引用。Sprite提供的destory会帮助设置内部引用为null。
例如,以下代码确保对象能够被作为垃圾回收:
var sp = new Sprite(); sp.destroy();
当对象设置为null,不会立即将其从内存中删除。只有系统认为内存足够低时,垃圾回收器才会运行。内存分配(而不是对象删除)会触发垃圾回收。
垃圾回收期间可能占用大量CPU并影响性能。通过重用对象,尝试限制使用垃圾回收。此外,尽可能将引用设置为null,以便垃圾回收器用较少时间来查找对象。有时(比如两个对象相互引用),无法同时设置两个引用为null,垃圾回收器将扫描无法被访问到的对象,并将其清除,这会比引用计数更消耗性能。
资源卸载
游戏运行时总会加载许多资源,这些资源在使用完成后应及时卸载,否则一直残留在内存中。
下例演示加载资源后对比资源卸载前和卸载后的资源状态:
var assets = []; assets.push("res/apes/monkey0.png"); assets.push("res/apes/monkey1.png"); assets.push("res/apes/monkey2.png"); assets.push("res/apes/monkey3.png"); Laya.loader.load(assets, Handler.create(this, onAssetsLoaded)); function onAssetsLoaded() { for(var i = 0, len = assets.length; i < len; ++i) { var asset = assets[i]; console.log(Laya.loader.getRes(asset)); Laya.loader.clearRes(asset); console.log(Laya.loader.getRes(asset)); } }
关于滤镜、遮罩
尝试尽量减少使用滤镜效果。将滤镜(BlurFilter和GlowFilter)应用于显示对象时,运行时将在内存中创建两张位图。其中每个位图的大小与显示对象相同。将第一个位图创建为显示对象的栅格化版本,然后用于生成应用滤镜的另一个位图:
应用滤镜时内存中的两个位图
当修改滤镜的某个属性或者显示对象时,内存中的两个位图都将更新以创建生成的位图,这两个位图可能会占用大量内存。此外,此过程涉及CPU计算,动态更新时将会降低性能(参见“图形渲染性能 – 关于cacheAs)。
ColorFiter在Canvas渲染下需要计算每个像素点,而在WebGL下的GPU消耗可以忽略不计。
最佳的做法是,尽可能使用图像创作工具创建的位图来模拟滤镜。避免在运行时中创建动态位图,可以帮助减少CPU或GPU负载。特别是一张应用了滤镜并且不会在修改的图像。
第4节:图形渲染性能
优化Sprite
1.尽量减少不必要的层次嵌套,减少Sprite数量。
2.非可见区域的对象尽量从显示列表移除或者设置visible=false。
3.对于容器内有大量静态内容或者不经常变化的内容(比如按钮),可以对整个容器设置cacheAs属性,能大量减少Sprite的数量,显著提高性能。如果有动态内容,最好和静态内容分开,以便只缓存静态内容。
4.Panel内,会针对panel区域外的直接子对象(子对象的子对象判断不了)进行不渲染处理,超出panel区域的子对象是不产生消耗的。
优化DrawCall
1.对复杂静态内容设置cacheAs,能大量减少DrawCall,使用好cacheAs是游戏优化的关键。
2.尽量保证同图集的图片渲染顺序是挨着的,如果不同图集交叉渲染,会增加DrawCall数量。
3.尽量保证同一个面板中的所有资源用一个图集,这样能减少提交批次。
优化Canvas