代码优化的目的是最大限度地提升你的Flash资源的性能,同时尽可能使用最少的系统资源-RAM和CPU。在本教程中,我们从一个可以运行但是耗费很多资源的Flash应用程序开始,然后逐渐地在我们的源代码中应用一些优化方法,最后得到一个更快,更简洁的SWF文件。
最终结果预览:
先看看我们要得到的最后的结果:
注意“MemoryUsed(内存使用)”以及“CPU Load(CPU 负荷)”这两个状态是基于你已经打开的浏览器窗口中的所有SWF文件,包括Flash广告以及相类似的一些东西。这个SWF文件与真实的它相比,这里就可能显得要消耗更多的资源。
第1步:理解这个Flash影片
这个Flash 影片有两个主要元素:一个是对火焰的例子模拟,另外一个是一张图,这张图显示的是这个动画随着时间变化的资源消耗情况。这张图的粉红色的线条是以兆字节为单位来跟踪这个影片的所有内存消耗,而绿色线条是以百分比的形式绘制出CPU的负荷情况。
ActionScript对象占用了系统分配给Flash Player的大部分内存,并且一个影片中包含的ActionScript对象越多,影片消耗的内存也就越多。为了让一个程序的内存消耗保存在低水平,Flash Player 一般会通过清理一些ActionScript 对象同时释放掉那些不再使用的内存来进行垃圾回收。
内存消耗图一般显示的是像一座小山一样的由上而下的这样一种图形,而后在每次执行垃圾回收之后,这个图像就变得倾斜,然后又随着新对象的创建慢慢上升。只上升的线条表明垃圾回收出现了问题,这意味着新的对象被添加到内存中,而没有其它的对象被删除。如果继续这样的一个趋势的话,Flash Player 可能最终因为耗尽了所有的内存而崩溃。
CPU负荷是通过跟踪影片的帧速来进行计算的。一个Flash 影片的帧速就有点类似于它的心跳。每一帧,Flash Player更新并且渲染屏幕上的所有元素,同时运行所有的要完成的ActionScript 任务。
帧速决定了Flash Player 在每一帧上该花多长的时间,所以10帧每秒(10fps)的帧速意味着至少每帧100毫秒。如果所有所需的任务在这样的一个时间段内完成了,那么Flash Player会一直等到本帧的剩余时间结束,才会开始下一帧。另一方面,如果以某一特定的帧速,所有所需的任务需要很高的CPU消耗以致于不能在相应的时间段内完成,那么帧速会自动减缓来获取一些额外的时间。一旦负荷减轻了,帧速又会自动加上去,返回到原来的设定值。
(在程序父窗口失去焦点或者移动到屏幕边界外面的情况下,Flash Player会自动地将帧速减小到4fps。这样做是确保无论用户的焦点在哪里,都能保护系统资源。)
所有的这些意味着实际上存在两种帧速:一种是你最初设置的并且希望你的影片总是以这个速度运行,另外一种是它实际的运行帧速。我们将称你自己设置的这个帧速叫做目标帧速,而它实际运行的速度称作实际帧速。
图中的CPU负荷是通过实际帧速和目标帧速的比值来进行计算的。使用到的公式如下:
CPU load=(目标帧速– 实际帧速 )/ 实际帧速 *100;
例如,如果目标帧速被设为50fps,但是影片实际的帧速为25fps,CPU 负荷将会是50%,计算方法是(50-25)/50*100.
请注意这并不是运行这个影片所要使用的系统CPU 资源的实际百分比,而是对真实值的一个初略的估计。对于这里描述的优化过程,这个估计对与手边的任务而言已经是一个很好的度量。为了可以获取到CPU 的实际使用率,可以使用你的操作系统提供的工具。例如,Windows中的任务管理器,现在看看我的任务管理器,它显示没有优化的影片正使用53%的CPU资源,而影片的图形上却显示CPU负荷为41.7%。
请注意:本教程中的所有影片截图都是来自我个人电脑上的Flash Player版本。这张图上的数字很可能在你的系统上显示的就不一样了,这主要依据你的操作系统,浏览器以及Flash Player版本。如果你已经在不同的浏览器窗口中或者Flash player中打开了其它的Flash 应用程序,那么在某些系统上它也可能影响到内存的使用大小。当分析你的程序的性能时,一定要确保没有其它的Flash 程序正在运行,因为它们可能影响到你的度量。
对于CPU 负荷而言,影片无论什么时候移动到了屏幕的边界之外,它的值就会飙升到90%。例如,如果你切换到另外一个浏览器或者将这一页往下翻,这是由更低的帧速造成的。更低的帧速不是由需要消耗大量CPU资源的任务造成的,而是无论什么时候,当该应用程序不在你的视野之内时,Flash就会压制帧速。
第2步:这些代码会让我的Flash工程变得更庞大吗?
这个影片的源代码在下面展示出来了并且只包含了一个类,类名叫做Flames,这个类也是文档类。这个类包含了一组的属性来跟踪这个影片的内存以及CPU负荷历史,跟踪到的数据用来绘制图形。内存和CPU负荷的统计信息是在Flames.getStatus()方法中计算并且更新的,图形是通过调用Flames.drawGraph()方法来进行绘制的。为了创建这个火焰效果,Flames.createParticles()方法首先每秒生成成百上千个粒子,这些粒子存放在fireParticles数组中,这个数组在Flames.drawParticles()方法中进行循环,利用每个粒子的属性来创建这个效果。
花一些时间来学习这个Flames类,你是否已经能找到一些快速的改变,而这些改变对程序的优化有很大的帮助呢?
package com.pjtops{
import flash.display.MovieClip;
import flash.events.Event;
import fl.motion.Color;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.text.TextField;
import flash.system.System;
import flash.utils.getTimer;
public class Flames extends MovieClip{
private var memoryLog = new Array(); // stores System.totalMemory values for display in the graph
private var memoryMax = 0; // the highest value of System.totalMemory recorded so far
private var memoryMin = 0; // the lowest value of System.totalMemory recorded so far
private var memoryColor; // the color used by text displaying memory info
private var ticks = 0; // counts the number of times getStats() is called before the next frame rate value is set
private var frameRate = 0; //the original frame rate value as set in Adobe Flash
private var cpuLog = new Array(); // stores cpu values for display in the graph
private var cpuMax = 0; // the highest cpu value recorded so far
private var cpuMin = 0; // the lowest cpu value recorded so far
private var cpuColor; // the color used by text displaying cpu
private var cpu; // the current calculated cpu use
private var lastUpdate = 0; // the last time the framerate was calculated
private var sampleSize = 30; // the length of memoryLog & cpuLog
private var graphHeight;
private var graphWidth;
private var fireParticles = new Array(); // stores all active flame particles
private var fireMC = new MovieClip(); // the canvas for drawing the flames
private var palette = new Array(); // stores all available colors for the flame particles
private var anchors = new Array(); // stores horizontal points along fireMC which act like magnets to the particles
private var frame; // the movieclips bounding box
// class constructor. Set up all the events, timers and objects
public function Flames() {
addChildAt( fireMC, 1 );
frame = new Rectangle( 2, 2, stage.stageWidth - 2, stage.stageHeight - 2 );
var colWidth = Math.floor( frame.width / 10 );
for( var i = 0; i < 10; i++ ){
anchors[i] = Math.floor( i * colWidth );
}
setPalette();
memoryColor = memoryTF.textColor;
cpuColor = cpuTF.textColor;
graphHeight = graphMC.height;
graphWidth = graphMC.width;
frameRate = stage.frameRate;
addEventListener( Event.ENTER_FRAME, drawParticles );
addEventListener( Event.ENTER_FRAME, getStats );
addEventListener( Event.ENTER_FRAME, drawGraph );
}
//creates a collection of colors for the flame particles, and stores them in palette
private function setPalette(){
var black = 0x000000;
var blue = 0x0000FF;
var red = 0xFF0000;
var orange = 0xFF7F00;
var yellow = 0xFFFF00;
var white = 0xFFFFFF;
palette = palette.concat( getColorRange( black, blue, 10 ) );
palette = palette.concat( getColorRange( blue, red, 30 ) );
palette = palette.concat( getColorRange( red, orange, 20 ) );
palette = palette.concat( getColorRange( orange, yellow, 20 ) );
palette = palette.concat( getColorRange( yellow, white, 20 ) );
}
//returns a collection of colors, made from different mixes of color1 and color2
private function getColorRange( color1, color2, steps){
var output = new Array();
for( var i = 0; i < steps; i++ ){
var progress = i / steps;
var color = Color.interpolateColor( color1, color2, progress );
output.push( color );
}
return output;
}
// calculates statistics for the current state of the application, in terms of memory used and the cpu %
private function getStats( event ){
ticks++;
var now = getTimer();
if( now - lastUpdate < 1000 ){
return;
}else {
lastUpdate = now;
}
cpu = 100 - ticks / frameRate * 100;
cpuLog.push( cpu );
ticks = 0;
cpuTF.text = cpu.toFixed(1) + '%';
if( cpu > cpuMax ){
cpuMax = cpu;
cpuMaxTF.text = cpuTF.text;
}
if( cpu < cpuMin || cpuMin == 0 ){
cpuMin = cpu;
cpuMinTF.text = cpuTF.text;
}
var memory = System.totalMemory / 1000000;
memoryLog.push( memory );
memoryTF.text = String( memory.toFixed(1) ) + 'mb';
if( memory > memoryMax ){
memoryMax = memory;
memoryMaxTF.text = memoryTF.text;
}
if( memory < memoryMin || memoryMin == 0 ){
memoryMin = memory;
memoryMinTF.text = memoryTF.text;
}
}
//render's a graph on screen, that shows trends in the applications frame rate and memory consumption
private function drawGraph( event ){
graphMC.graphics.clear();
var ypoint, xpoint;
var logSize = memoryLog.length;
if( logSize > sampleSize ){
memoryLog.shift();
cpuLog.shift();
logSize = sampleSize;
}
var widthRatio = graphMC.width / logSize;
graphMC.graphics.lineStyle( 3, memoryColor, 0.9 );
var memoryRange = memoryMax - memoryMin;
for( var i = 0; i < memoryLog.length; i++ ){
ypoint = ( memoryLog[i] - memoryMin ) / memoryRange * graphHeight;
xpoint = (i / sampleSize) * graphWidth;
if( i == 0 ){
graphMC.graphics.moveTo( xpoint, -ypoint );
continue;
}
graphMC.graphics.lineTo( xpoint, -ypoint );
}
graphMC.graphics.lineStyle( 3, cpuColor, 0.9 );
for( var j = 0; j < cpuLog.length; j++ ){
ypoint = cpuLog[j] / 100 * graphHeight;
xpoint = ( j / sampleSize ) * graphWidth;
if( j == 0 ){
graphMC.graphics.moveTo( xpoint, -ypoint );
continue;
}
graphMC.graphics.lineTo( xpoint, -ypoint );
}
}
//renders each flame particle and updates it's values
private function drawParticles( event ) {
createParticles( 20 );
fireMC.graphics.clear();
for ( var i in fireParticles ) {
var particle = fireParticles[i];
if (particle.life == 0 ) {
delete( fireParticles[i] );
continue;
}
var size = Math.floor( particle.size * particle.life/100 );
var color = palette[ particle.life ];
var transperency = 0.3;
if( size < 3 ){
size *= 3;
color = 0x333333;
particle.x += Math.random() * 8 - 4;
particle.y -= 2;
transperency = 0.2;
}else {
particle.y = frame.bottom - ( 100 - particle.life );
if( particle.life > 90 ){
size *= 1.5;
}else if( particle.life > 45){
particle.x += Math.floor( Math.random() * 6 - 3 );
size *= 1.2;
}else {
transperency = 0.1;
size *= 0.3;
particle.x += Math.floor( Math.random() * 4 - 2 );
}
}
fireMC.graphics.lineStyle( 5, color, 0.1 );
fireMC.graphics.beginFill( color, transperency );
fireMC.graphics.drawCircle( particle.x, particle.y, size );
fireMC.graphics.endFill();
particle.life--;
}
}
//generates flame particle objects
private function createParticles( count ){
var anchorPoint = 0;
for(var i = 0; i < count; i++){
var particle = new Object();
particle.x = Math.floor( Math.random() * frame.width / 10 ) + anchors[anchorPoint];
particle.y = frame.bottom;
particle.life = 70 + Math.floor( Math.random() * 30 );
particle.size = 5 + Math.floor( Math.random() * 10 );
fireParticles.push( particle );
if(particle.size > 12){
particle.size = 10;
}
particle.anchor = anchors[anchorPoint] + Math.floor( Math.random() * 5 );
anchorPoint = (anchorPoint == 9)? 0 : anchorPoint + 1;
}
}
}
}
复制代码
有很多东西要理解,但是不要担心-----我们将会在教程的剩下部分对这些代码进行不同的改进。
第3步:使用强类型给所有的变量指定数据类型
我们对这个类做的第一个改进的地方是给所有声明的变量,函数参数以及函数返回值指定数据类型。
例如,将下面的代码
protected var memoryLog = new Array();
protected var memoryMax = 0; // yes, but what exactly are you?
复制代码
改成
protected var memoryLog:Array = new Array();
protected var memoryMax:Number = 0; // memoryMax the Number, much better!
复制代码
无论什么时候声明变量,一定要指定它们的数据类型,因为这可以让Flash 编译器在生成SWF文件的时候执行一些特殊的优化。单独这一个就可以带来很大的性能提升,我们将很快从我们的例子中看到效果。另外一个使用强类型的好处是编译器将会捕捉并且对任何与数据类型有关的bug会做出警告。
第4步:检查结果
这张屏幕截图显示的内容是经过上面的优化之后Flash 影片的性能,我们可以看到当前的或者最大的CPU负荷并没有因为上面的优化而发生改变,最小CPU负荷从8.3%降到了4.2%。最大的内存消耗从9MB减少到了8.7MB。
相比步骤2中显示的图片中的内存线条,上图中的内存线条的倾斜度也发生了改变。它仍然呈现出相同的锯齿形的形状,但是现在它以一个很缓慢的速率下降和上升。如果你认为内存消耗的突然下降是由Flash Player的垃圾回收造成的,而内存消耗的突然下降事件常常在分配的内存即将耗尽时触发,那么这是一件很棒的事情。这样的垃圾回收可能会是一次代价非常高的操作,因为Flash Player必须遍历所有的对象,寻找那些不再需要但仍然占用内存的对象。这样的操作做的次数越少,对整个应用程序而言性能就越好。
第5步:高效地存储数值数据
ActionScript提供了3中数值数据类型,Number,uint以及int。在这三种类型中,Number消耗的内存最多,因为与其它两种类型相比,它可以存储更大的数值。而且它也是能唯一存储十进制小数的数据类型。
Flame 类有很多的数值属性,所有的这些属性使用的都是Number数据类型。由于int和uint是更轻便的数据类型,我们可以通过在所有的场合中使用它们而不是使用Number来节省一些内存,当然这些场合中我们是不需要使用到十进制小数的。
一个比较好的例子是在循环和数组索引中,例如我们将把下面的代码
for( var i:Number = 0; i < 10; i++ ){
anchors[i] = Math.floor( i * colWidth );
}
复制代码
改成
for( var i:int = 0; i < 10; i++ ){
anchors[i] = Math.floor( i * colWidth );
}
复制代码
cpu,cpuMax以及memoryMax的属性将仍然使用Number数据类型,因为它们很有可能要存储十进制数据,而memoryColor,cpuColor以及ticks可以改成uint类型,因为它们将用来存储正整数。
第6步:进行最少的函数调用
函数调用的代价非常高,尤其是调用另外一个类中的函数。如果那个类是在不同的包中或者该函数为一个静态方法(函数),那么情况就会变得更糟。这里的Math.floor()函数就是一个非常好的例子,它在Flames类中被用来对小数值进行四舍五入。这个函数调用可以通过使用uint而不是使用Number来存储整数的方法进行避免。
// So instead of having:
anchors[i] = Math.floor( i * colWidth );
// we instead cast the value to a uint
anchors[i] = uint( i * colWidth );
// The same optimization can be performed by simply assigning the uint data type, for example changing
var size:uint = Math.floor( particle.size * particle.life/100 );
// into
var size:uint = particle.size * particle.life/100;
复制代码
在上面的例子中,调用Math.floor()函数不是必须的,因为Flash会自动地在将十进制小数值转换成uint型数值时进行四舍五入。
第7步:乘法比除法快多了
Flash Player明显地发现乘法运算比除法运算快得多,所以我们寻找整个Flames 类并且将所有的除法运算转换成等价的乘法运算。转换公式涉及到获取操作符右边的值的倒数,并且用该倒数乘以操作符左边的数值。一个数的倒数等于1除以这个数。
var colWidth:uint = frame.width / 10; //division by ten
var colWidth:uint = frame.width * 0.1; //produces the same result as multiplication by 0.1
复制代码
让我们快速地看看我们最近几步优化的结果吧。CPU负荷终于从41.7%降到了37.5%,但是内存消耗却呈现出不一样的变化。最大的内存消耗已经上升到了9.4MB,这是目前为止最高的数值了,并且图中尖尖的,锯齿形的线条表明垃圾回收现在执行的非常频繁。一些优化技术将会对内存和CPU负荷产生副作用,改善了其中一个而把另一个作为代价。由于内存消耗基本上是回到了最开始的值,所以还有很多的工作要做啊!
第8步:循环利用有利于整个环境
你也可以在改善程序环境上做好你的工作。当写AS3代码的时候重复利用你的对象可以减少程序中资源消耗的数量。新对象的创建和销毁要付出的代价很高。如果你的程序经常创建和销毁同一类型的对象,那么通过重复利用这些对象可以获取到很大的性能提升。看看Flames类,我们可以看到每一秒都有很多的例子对象被创建和销毁。
private function drawParticles( event ):void {
createParticles( 20 );
fireMC.graphics.clear();
for ( var i:* in fireParticles ) {
var particle:Object = fireParticles[i];
if (particle.life == 0 ) {
delete( fireParticles[i] );
continue;
}
复制代码
有很多的方法来重复利用对象,大部分都涉及到创建一个第二变量来存储不需要的对象,而不是删除它们。然后当需要同一类型的新对象时,它可以从存储的数据中重新获取而不需要重新创建一个新的对象。新的对象只在没有存储数据的时候才被创建。我们将对Flames类的粒子对象做些类似的优化。
首先,我们创建一个新的数组,数组名为inactiveFireParticles[],这里面存放着粒子的引用,这些例子的life属性都设为了0.(没有生命)在drawParticles()函数中,不需要删除一个没有生命的粒子,我们将它添加到inactiveFireParticles[]数组中。
private function drawParticles( event ):void {
createParticles( 20 );
fireMC.graphics.clear();
for ( var i:* in fireParticles ) {
var particle:Object = fireParticles[i];
if( particle.life <= 0 ) {
if( particle.life == 0 ){
particle.life = -1;
inactiveFireParticles.push( particle );
}
continue;
}
复制代码
接着我们修改cureateParticles()方法来对存放在inactiveFireParticles()数组进行第一次的遍历,并且在创建新的粒子之前使用它们。
private function createParticles( count ):void{
var anchorPoint = 0;
for(var i:uint = 0; i < count; i++){
var particle:Object;
if( inactiveFireParticles.length > 0 ){
particle = inactiveFireParticles.shift();
}else {
particle = new Object();
fireParticles.push( particle );
}
particle.x = uint( Math.random() * frame.width * 0.1 ) + anchors[anchorPoint];
particle.y = frame.bottom;
particle.life = 70 + uint( Math.random() * 30 );
particle.size = 5 + uint( Math.random() * 10 );
if(particle.size > 12){
particle.size = 10;
}
particle.anchor = anchors[anchorPoint] + uint( Math.random() * 5 );
anchorPoint = (anchorPoint == 9)? 0 : anchorPoint + 1;
}
}
复制代码
第9步:在任何可能的情况下使用Object和Array字面值
当创建新的对象或者数组是,使用文字型语法比使用new操作符更快。
private var memoryLog:Array = new Array(); // array created using the new operator
private var memoryLog:Array = []; // array created using the faster array literal
particle = new Object(); // object created using the new operator
particle = {}; // object created using the faster object literal
复制代码
第10步:避免使用动态类
ActionScript中的类可以使密封的也可以是动态的。它们默认是密封的,这意味着一个对象能从类中唯一获取到的属性和方法必须在类中进过了定义。而对于动态类而言,新的属性和方法可以再运行时进行添加。密封类比动态类更高效,因为当一个类的所有可能的功能都已经知道了的话,那么一些Flash Player的性能优化就可以进行了。
在Flames 类中,成千上万个粒子是继承自内建的Object类,这个类是动态类。由于没有新的属性在运行时需要被添加到粒子中,我们将通过给粒子创建一个自定义的密封类来节省更多的资源。
下面的是新的Particle,这已经被添加到同一个Flames.as文件中去了。
class Particle{
public var x:uint;
public var y:uint;
public var life:int;
public var size:Number;
public var anchor:uint;
}
复制代码
CreateParticles()方法也将作出调整,将下面的这两行
var particle:Object;
particle = {};
复制代码
改成
var particle:Particle;
particle = new Particle();
复制代码
第11步:当你不需要时间轴的时候就使用Sprites
就和Object类一样,MovieClips属于动态类,MovieCliip类继承自Sprite类,并且这两者之间最主要的不同点在于MovieClip有时间轴。因为Sprite涵盖了MovieClip中除时间轴外的所有功能,在你的显示对象不需要时间轴的任何时候都尽量使用Sprite.Flames类继承自MovieClip,但是它并不使用时间轴,因为它的所有的动画都是通过ActionScript来控制的。火焰离子是在fireMC中绘制的,这也是一个不需要时间轴的影片剪辑。
我们将Flames和fireMC都改成继承自Flames和fireMC,下面的代码
import flash.display.MovieClip;
private var fireMC:MovieClip = new MovieClip();
public class Flames extends MovieClip{
复制代码
可以被替换成
import flash.display.Sprite;
private var fireMC:Sprite = new Sprite();
public class Flames extends Sprite{
复制代码
第12步:当你不需要子显示对象或者鼠标输入的时候,使用Shape来代替Sprite
Shape类比起Sprite类来更显得轻便,但是他不支持鼠标事件或者包含子显示对象。由于fireMC并不需要这些功能,我们可以安全地将它装换成Shpae类型。
import flash.display.Shape;
private var fireMC:Shape = new Shape();
复制代码
这张图显示内存消耗上我们得到了很大的改进,数值下降并且稳定地保持在4.8MB这个水平线上。锯齿线也被近乎水平直线所替代,意味着垃圾回收现在很少运行。但是CPU负荷又基本上回到了起始水平41.7%。
第13步:避免循环内的复杂计算
他们说一个程序超过50%的时间是用来执行它10%的代码,并且而那10%中的大部分时间都花在了循环上。很多循环优化技术涉及到将很多高CPU消耗的操作移到循环体的外面。这些操作包括对象的创建,变量的查找以及计算。
for( var i = 0; i < memoryLog.length; i++ ){
// loop body
}
复制代码
上面显示的drawGraph()方法中的第一个循环。这个循环遍历了memoryLog数组中的每一项,利用这些数据来在图中绘制出每一个点。在每次开始运行的时候,它找到memoryLog数组的长度并且将它和循环计数值的大小相比较。如果memoryLog数组有200项,这个循环就要执行200次,并且执行200次同样的查找。因为memoryLog的长度并不会改变,那么重复的查找就显得很浪费并且是没有必要的。最好是在查找开始之前一次找出memoryLog.length的值,并把它存到一个本地变量中,因为访问一个本地变量比访问一个对象的属性要快得多。
var memoryLogLength:uint = memoryLog.length;
for( var i = 0; i < memoryLogLength; i++ ){
// loop body
}
复制代码
在Flames类中,我们如上面所示地调整了drawGraph()方法中的两个循环。
第14步:将条件语句中最可能为真的语句放到第一位
考虑下面的if…else条件语句区块,它们是drawParticles()方法里面的一部分:
if( particle.life > 90 ){ // a range of 10 values, between 91 - 100
size *= 1.5;
}else if( particle.life > 45){ // a range of 45 values, between 46 - 90
particle.x += Math.random() * 6 - 3;
size *= 1.2;
}else { // a range of 45 values, values between 0 - 45
transperency = 0.1;
size *= 0.3;
particle.x += Math.random() * 4 - 2;
}
复制代码
一个粒子的生命可以使0到100之间的任何一个值。If语句测试当前的例子生命是否是在91到100之间,并且如果是的话,它就会执行相应区块下的代码。else----if字句测试粒子的生命值是否在46到90之间,然后else字句测试的是剩下的值,这些值的氛围是在0到45之间。考虑到第一个检测成功的可能性最小,因为它只有很小的数值范围,它应该放在最后来进行条件测试。这个if—else区块在下面进行了重写,这样的话最可能成功的情况最先检测,这就使得计算更高效。
if( particle.life < 46 ){
transperency = 0.1;
size *= 0.3;
particle.x += Math.random() * 4 - 2;
}else if( particle.life < 91 ){
particle.x += Math.random() * 6 - 3;
size *= 1.2;
}else {
size *= 1.5;
}
复制代码
第15步:不使用push来将元素添加到数组的末尾
Array.push()方法在Flames类中用到很多。它将会被一个更快速的方法替代,这个方法使用到了数组的length属性。
cpuLog.push( cpu ); // slow and pretty
cpuLog[ cpuLog.length ] = cpu; // fast and ugly
复制代码
当我们知道数组的长度,我们可以用一种更快的技术来替代Array.push()方法,下面是示例:
cpuLog.push( cpu ); // slow and pretty
cpuLog[ cpuLog.length ] = cpu; // fast and ugly
复制代码
第16步:用Vectors替代Arrays
Array和Vecotr两个类非常相似,但也有两个不同点:Vecotrs只能存储同一类型的对象,并且他们比数组更高效,更快。由于Flames类中所有的数组只是根据需要存储一种类型的变量---int,uint或者Particle---我们可以将它们全部转换成Vectors.
这些数组:
private var memoryLog:Array = [];
private var cpuLog:Array = [];
private var fireParticles:Array = [];
private var palette:Array = [];
private var anchors:Array = [];
private var inactiveFireParticles:Array = [];
复制代码
可以用等价的Vector进行替换:
private var memoryLog:Vector.<Number> = new Vector.<Number>();
private var cpuLog:Vector.<Number> = new Vector.<Number>();
private var fireParticles:Vector.<Particle> = new Vector.<Particle>();
private var palette:Vector.<uint> = new Vector.<uint>();
private var anchors:Vector.<uint> = new Vector.<uint>();
private var inactiveFireParticles:Vector.<Particle> = new Vector.<Particle>();
复制代码
然后我们修改getColorRange()方法来正常使用Vectors而不使用数组。
private function getColorRange( color1, color2, steps):Vector.<uint>{
var output:Vector.<uint> = new Vector.<uint>();
for( var i:uint = 0; i < steps; i++ ){
var progress:Number = i / steps;
var color:uint = Color.interpolateColor( color1, color2, progress );
output[i] = color;
}
return output;
}
复制代码
第17步:谨慎使用事件模式
虽然很方便,快捷,但是AS3的事件模式是建立在复杂的事件侦听,发送以及对象上面的;然后就有了事件的传送,冒泡以及更多操作,所有的这些在教科书上应该会有讲到。无论什么时候只要有可能,总是直接去调用一个方法而不使用事件模型。
addEventListener( Event.ENTER_FRAME, drawParticles );
addEventListener( Event.ENTER_FRAME, getStats );
addEventListener( Event.ENTER_FRAME, drawGraph );
复制代码
Flames类有三种事件侦听器,分别调用三种不同的方法,并且都绑在了ENTER_FRAME事件上。在这种情况下,我们可以执行第一个事件侦听器而不需要另外两个,然后先是执行drawParticles()方法,然后是getStarus()方法,最后调用drawGraph()方法。作为选择,我们可以只创建一个新的方法,这个方法为我们直接调用getStatus(),drawGraph()以及drawParticles(),然后只有一个事件侦听器绑定到这个新的方法上。可是第二种做法的代价更高,所以我们还是选择第一种做法。
// this line is added before the end of the <code> drawParticles </code>() method
getStats();
// this line is added before the end of the <code> getStats() </code> method
drawGraph();
复制代码
我们也将从drawGraph()和getStatus()方法中移除事件参数(这个参数持有事件对象),因为它们已经不再需要了。
第18步:给所有不需要鼠标事件的显示对象关闭它们的鼠标事件相应功能
由于这个Flash动画并不需要进行任何的用户交互,我们可以关闭掉显示对象的发送鼠标事件功能。在Flames类中,我们通过将它的mouseEnabled属性设置为false来达到目的。我们也通过设置mouseChildren属性为false来禁止它所有的子对象的鼠标响应事件功能。把下面的代码添加到Flames构造函数中。
mouseEnabled = false;
mouseChildren = false;
复制代码
第19步:使用Graphics.drawPath()方法来绘制复杂的形状
当使用很多的直线或者曲线来绘制复杂的路径时,Graphics.drawPath()方法可以用来进行性能优化,在Flames.drawGraph()方法中,CPU负荷以及内存消耗图线都是使用Graphics.moveTo()和Graphics.lineTo()方法组合来进行绘制的。
for( var i = 0; i < memoryLogLength; i++ ){
ypoint = ( memoryLog[i] - memoryMin ) / memoryRange * graphHeight;
xpoint = (i / sampleSize) * graphWidth;
if( i == 0 ){
graphMC.graphics.moveTo( xpoint, -ypoint );
continue;
}
graphMC.graphics.lineTo( xpoint, -ypoint );
}
复制代码
我们调用Graphics.drawPath()方法来替代最初的绘制方法。经过改进了的代码的一个额外的好处是我们也将绘图代码从循环中移除了。
var commands:Vector.<int> = new Vector.<int>();
var data:Vector.<Number> = new Vector.<Number>();
for( var i = 0; i < memoryLogLength; i++ ){
ypoint = ( memoryLog[i] - memoryMin ) / memoryRange * graphHeight;
xpoint = (i / sampleSize) * graphWidth;
if( i == 0 ){
data[ data.length ] = xpoint;
data[ data.length ] = -ypoint;
commands[ commands.length ] = 1;
}
data[ data.length ] = xpoint;
data[ data.length ] = -ypoint;
commands[ commands.length ] = 2;
}
graphMC.graphics.drawPath( commands, data );
复制代码
第20步:最终完成这些类
Final属性定义了一个不可以被重写的方法或者不能被继承的类。它也可以让一个类执行的更快,所以我们将Flames和Particle类的属性设为final。
编者按:读者Moko给我们展示了一篇由Jackson Dunstan写的很棒的文章,这篇文章中提到了final关键字并不会对性能有任何的影响。
CPU 负荷现在是33.3%,而所有的内存使用保持在4.8到5MB之间。从最初的CPU负荷为41.7%以及最大的内存消耗为9MB到现在的状态,我们走过了一段很长的路程。
知道什么时候停止,它给我们带来了在优化的过程中要做的最重要的决定之一。如果你停止的太早,你的游戏或者应用程序可能在低端系统上面执行的性能会比较差,并且如果你停止的太晚,你的代码可能会变得很模糊并且很难去维护。对于这个特定的应用程序,动画看起来很平滑很流畅,同时CPU和内存使用的情况也很正常,所以我们在这里停止。
总结:
我们使用Flames类作为一个例子来展示这样一个优化过程。同时很多的优化技巧通过一步一步的改变展现出来了,它们之间的顺序没有任何的关系。最重要的是要理解很多可以让我们的程序变慢的问题,并且采取措施来改正它们。
但是记住要当心过早的优化。首先应该专注于构建你的程序并且让它运行起来,然后才开始进行优化。