梦星痕

--记录生活与工作中的点点滴滴
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

【收集】关于 Flash 项目性能优化(转贴)

Posted on 2013-04-25 17:05  梦星痕  阅读(128)  评论(0)    收藏  举报

内存优化在项目是一个重要的环节,如果不合理的利用和回收内存会合你的程序整体大大下降.

【小记录一下】

AS3内要取得指定对象占用的空间(一般多指较为低级的对象),的使用 getSize() 函数,

引入 import flash.sampler.getSize;
例如:var a:int;
trace(getSize(a));

合理使用对象

创建不同对象一般所消耗的内存是不一样的。如:Number 消耗 8 个字节,int消耗 4个字节, uint消耗 4个字节.下面我举一些例子:

1.int 类可使用表示为 32 位带符号整数的数据类型。 int 类表示的值的范围是:-2,147,483,648 (-2^31) 到 2,147,483,647 (2^31-1),所以如果你的取值范围在-2,147,483,648 (-2^31) 到 2,147,483,647 (2^31-1)请你用int而不是用Number(刚从2.0转过来的人可能喜欢用Number).uint 范围是0 到 4,294,967,295 (2^32-1) 之间,Actionscript3中类型很少,所以这些只要你平时稍加注意一下就行.


2.合理使用Shape与Sprite,MovieClip,你可能用MovieClip可以完成Sprite与Shape的功能,但是他们所需的内存是不一样的Shape需要 236 字节,Sprite 需要 412字节, MovieClip 需要440字节,如果你只想显示图形没有交互那你使用Shape,如果是有交互的图形你可以用Sprite,如果是动画你才用MovieClip.

以上只是2个常见的实例,其实在as3中还有很多值得注意的这类情况。我还看到有些大哥为了派发一个事件而去继承Sprite类,Sprite需要消耗400字节, EventDispatcher只需要40字节.
对象重用

简单的说就是重复使用对象,而不是释放再重新申请,你可能这么认为:释放一个对象回收100字节的空间,我重新在new一个又占用100字节,返正都得占用100字。在flashplayer中不是说你想释放就能释放的,垃圾回收是由flashplayer来执行的,程序是不能控制,更不知道他是什么时候来执行的.所以对于我们来说回来是完全不可控的.所以说你觉得可回收了对象,很可能没回收继续占用空间直到flashplayer觉得内存不够时才可能执行垃圾回收从而被释放.而执行垃圾回收是非常耗费资源的操作尤其在大型的项目中.

虽然我们不能控制垃圾回收,但是我们可以降低垃圾回收器执行的次数,这就是我们尽量做到不去new,而使用现有的对象。这些做还有个好处就是,节省了创建对象的性能开销.

这里是不断的创建对象

var size:Rectangle;
var bitmap:BitmapData=new BitmapData(100,100);
for (var:int = 0; i < 100; i++)
{
    size = new Rectangle(i,0,1,10);
    myBitmapData.fillRect(size,COLOR);
}

重用对象

var bitmap:BitmapData=new BitmapData(100,100);
var size:Rectangle = new Rectangle(0,0,1,10);
for (var:int = 0; i < 100; i++)
{
    size.x = i;
    myBitmapData.fillRect(size,COLOR);
}

 对象池技术

对象池技术的原理就是回收不使用的对象而不是遗弃等待FlashPlayer垃圾回器的执行,等到再需要时再拿来使用.这种技术使用非常广泛,看下面的简单的实现(这里只是一个抛砖引玉,具体怎么设计这个对象池,要看各位的具体项目了).

IRecyclable接口,可以被回收对象必须实现。再就是ObjectPool.

package
{
     public interface IRecyclable
     {
         function dispose():void;
     }
}
//对象池的实现 package
{
public class ObjectPool
{
private var pool:Vector.<IRecyclable>; private var type:Class;//回收对象的类型 public function ObjectPool(type:Class) { this.type=type; pool=new Vector.<IRecyclable>(); }
public function addObject(o:IRecyclable):void
{ o.dispose(); pool.push(o); }
public function getObject():*
{ if(pool.length<=0) return new type(); return pool.pop(); } }
}

 


对象的另类存储

在存储大量对象时,我们可以以另一种方式存储或者叫序列化,即存储对象的数据而不存储具体的对象,当需要时再根椐需要数据返序列化出一个对象。这样做法的好处在于不会有大量的对象产生,在as中对即使是空对象也会占用40个字节.

很多时候你可能这样写代码:

var p:Array=[];
for(var i:int=0; i < 1000000; i++)
{ p.push(
new Point(i,i)) }

这里我来实现另一种对象存储:

package
{
    import flash.geom.Point;
    public class PointContainer
    {
        private var container:Array
        public function PointContainer()
        {
            container=[];
        }
public function add(v:Point):void
{ container.push(v.x); container.push(v.y); }
public function getPointAt(index:int):Point
{
return new Point(container[index],container[index+1]); } } } //使用 var p:PointContainer = new PointContainer(); for(var i:int=0;i<1000000;i++)
{ p.add(
new Point(i,i)) } for(var i:int=0;i<1000000;i++)
{ p.getPointAt(i); }

这种对象存储方式适合大量小型对象存储,比如粒子系统.如果是庞大的对象,这种方式没有任何优式.


事件优化

使用事件模型通信与使用传统的回调函数相比,速度更慢且占用的内存更多

AS3中事件的派发传递参数是采用Event,所以在高频率派发事件的地方你可以采用传统的回调函数这样可以大大提高你的效率和内存消耗.
位图优化

一般最占用内存的部分就是位图,在我开发的MMO游戏中90%以上的的内存是由位图占据的,所以在位图的使用过程序要特别注意,不使用的位图一定要释放掉。在这里我提一些小的建议,以尽量控制位图的内存占用。

1.能共享位图的尽量共享,具体做法就是用一个BitampData创建多个Bitamp对象。尽量不要去复制BitmapData对象.
2.将滤镜应用于显示对象时,Flash Player 将在内存中创建两个位图,所以这需要大量内存。所以尽量不要去使用滤镜,一般可以用ps做好滤镜后生成位图给flash来使用.
3.合理的使用位图缓存.对矢量图形做位图缓存,其实在把矢量图形变成位图,并使用该

位图进行呈现,此会显著提高呈现的性能,但需要占用大量内存。针对复杂的矢量内容使用位图缓存功能。
释放对象

释放对象其实只有一句话,就是不支有对象的引用,包括声音/视频流,socket,件事等等.我最多的一种情况是事件忘记移除导致对象无法回收,这并不是我不知道这一点,而是在写代码时的疏忽。如果你是一个人开发,你可能经常去profile你的代码,可能很容易找出哪个地方没被移除,但是如果你主程或者架构师你手下有很多少人在Coding,你怎么让他不遗忘移除事件呢,下面我来简单介绍一种方法一次性移除所有事件,避免一个一个移除带来的遗漏问题.

一般大家都用EventDispatcher来派发事件.现在我们就对addEventListener进行一个小小的改造即可.

package
{
    import flash.events.EventDispatcher;
    import flash.events.IEventDispatcher;

    public class MyEventDispatcher extends EventDispatcher
{
private var events:Array public function MyEventDispatcher(target:IEventDispatcher=null) { super(target); events=[]; }
override
public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void
{ events.push({type:type,fun:listener}) super.addEventListener(type, listener, useCapture, priority, useWeakReference); }
override
public function removeEventListener (type:String, listener:Function, useCapture:Boolean = false):void
{ super. removeEventListener(type,listener, useCapture); for(var i:int=0;i< ;i++)
{
if( events[i].type==type && events[i].fun==listenner)
{ events.splice(i,
1) } } }
public function dispose():void
{ var ev:Object; while(events.length)
{ ev
=events.pop(); super.removeEventListener(ev.type,ev.fun);
} } } }

所以你在使用EventDispatcher的地方全部使用MyEventDispatcher即可,在回收之前端调用一下dispose方法,就会内部移除所有事件.

还有很多方法可以做到这一点,以上方法只是一个抛砖引玉.
滥用强制垃圾回收

在Flashplayer debug版本提供了System.gc()接口,可以让虚拟机执行垃圾回收,但是flashplayer 普通用户版是没有这个接口的,于是有人想出使用异常还出发垃圾回收如:

function gc():void
{ try { (new LocalConnection).connect("foo"); (new LocalConnection).connect("foo"); }
catch(e)
{ trace(System.totalMemory); } }

当然还有其他方法在这里我就不多举了.

Adobe为什么在普通用户版的flashplayer中取消对System.gc()接口的支持,adobe肯定不希望用户直接去触发垃圾回器,肯定是有理由的,下面我将对这个理由进行的浅薄分析.

垃圾回收器通过查找系统中的相互引用,从而检测出处于非活动状态的对象。将删除通过这种方式检测到的处于非活动状态的对象。也就说他要扫描可能持有对象的的所有变量,这一点所需的代价很大,尤其是在大型项目中.如果你经常去做这样的事,这会大大浪费你的CPU资源.

我做Flash开发已快5年了,我没有什么地方非要用到强制垃圾回收的地方.所以我要在这里提醒广大Flash开发者,在没有特术需求的时候不要使用强制垃圾回收,不要会了腾出一点内存空间而去强制垃圾回收,可能很多时候你是检芝麻丢西瓜,回收器的执行不是普通程序员管的事,Flashplayer会选择最合适的时候去调用.(原本这一节不是我要写的内容,原因是我看到在天地会主页很醒目的位置有一篇《AS3强制内存回收方法之二》,而且受到大家的广泛关注,所以我不想让这篇文章误导了一些新手,以上只是个人见解我希望大家一起讨论这个问题)

以下为评论的转载

--------------------------------------------------------------------------------------
guissy 2010-12-11 12:32
1.优先级 int uint Number
优先级 EventDispatcher Shape Sprite MovieClip
2.for 或EnterFrame或TIMER里边少用new
3.闲置对象=null 不如 ObjectPool.add(闲置对象)
4.[new Point(0,0),new Point(1,1)]不如 [0,0,1,1]


guissy 2010-12-13 20:32
关于对象池,考虑使用new Dictionary(true),具备弱引用。
思路见贴:http://bbs.9ria.com/redirect.php?goto=findpost&pid=561614&ptid=63490

lcj0526 2010-12-14 09:50
另外关于对象池的看法,我提点个人意见,其实我觉得只要你不是在同时进行大量new的话,我并不赞成采用对象池的方法,加了对象池,会额外增加swf文件的大小,尽管不一定很多,另外,因为不是大量的生成,所以你new一个的对象所需的时间,可能比你去取对象池的速度还快。所以一般对象池技术是用于服务器的,除非客户端像你所说要做粒子效果的话,采用这个倒是不错,呵呵。以上纯属个人看法,不当之处还请高人指点

青竹 2010-12-14 10:25
谢谢这位兄弟的评点,对象池技术不只用在服务器,在设计中高密度创建的对象地方,对象池技术可以减小创建与销毁的代价.

guissy 2010-12-13 17:36
1.位图优化。如果是mmo之类需要严格控制内存的,这是非常值得关注的技术。如果是网页特效或一些全屏的小应用,还是矢量为佳.
2.override addEventListener 与某人说的“事件总线”,都存在性能上和编码习惯的不友好。必要时使用弱引用,这是比较好的习惯。另外, 框架式的代码必须用profile去检测.每个类都用dispose()做销毁工作也是相当好的习惯。赞同。
3.gc是自动管理内存。但不觉得gc能占用多少cpu.除非你的是循环着用.如果真有有那么费cpu,请截图.

lcj0526 2010-12-14 09:44

呵呵,你这个占CPU的我测试过,在我的项目里如果每桢都调用gc方法的话,CPU会一直在13以上,如果不调用的一般就是在0-5之间。我指的是在同种情况下,具体就是玩家不动,当前视野里没其他对象操作的情况下测试的。另外CPU占用的多少与计算机的配置也是有很大的关系的

/*****************************************

1、在局部变量够用时,不要使用全局变量。类静态变量也要少用。全局变量是开发者的恶梦。实在需要全局变量的话,我建议使用
singleton设计模式来进行管理。

2、读取数组中的数据,尽量使用for in 循环来代替传统的 for 或者while循环语法。
因为for in的执行效率比起后者提高了30%。其次是选择while循环,传统for循环语法的效率在三者中是最低的。
向数组中写入数据,则是while循环稍占优势。

3、给所有的MovieClip都多加一个空白帧,当他们不出现在屏幕上时,你能用gotoAndStop()语句跳转到这一帧。为什么呢?因为播放器仍然
要消耗一定的资源来处理这些不可见/屏幕外的mc,哪怕他们是单帧,非播放的状态。简单的设置可见度属性为不可见( _visible = false )
是无效的,播放器依然会按照这些MC说停留或播放的帧的复杂度来分配资源。

4、能用单行赋值则使用单行赋值。比如:
a = 0
b = 0
c = 0
效率就不如:
a = b = c = 0

5、变量名越短,效率越高。考虑到长变量名也有它的好处(比如,便于维护等),因此建议在关键部位(比如大量循环出现的时候)使用短
变量名,最好就1-2个字符。

6、不要过多使用 Object 类型。 数据类型注释应力求精确,这样可以提高性能。只有在没有适当的备选数据类型时,才使用 Object 类型
。同时也便于代码管理,时刻知道对象的类型和作用。同时也有利于编译器编译时优化。

7、避免使用 eval_r() 函数或数据访问运算符。通常,较为可取且更有效的做法是只设置一次局部引用。不得已时才用eval,比如转换
_droptarget为MovieClip时。

8、在开始循环前将 Array.length 赋予变量,尤其是大的循环。 在开始循环前将 Array.length 赋予变量(比如var iLength:Number),将
其作为条件使用,而不是使用myArr.length 本身。原因,在循环中,iLength是Number变量,会被放入寄存器使用,效率远比访问Array再得
到length高。

9、当用到复杂的条件表达式时。把他们打散成为嵌套的独立判断结构是最佳方案。比如if(a&&b&&c)的效率就低于if(a){if(b){if(c){}}}。
当然这样做代码是会比较不美观。

10、在用到矢量图的时候,我们要尽可能简化它们的形状,去除多余的端点。这样做将大大降低播放器用于呈现矢量图所要进行的计算量。

11、需要呈现比较复杂的场景时,你就最好考虑使用位图实现。
虽然Flash在对位图的渲染效率上并不是最优越的(比如和Flash的“兄长”Director比起来),但丰富的视觉内容呈现只能靠位图(与位图同复
杂度的矢量图形渲染速率非常低)了,这也是很多基于区块的游戏中广泛采用像素图作为背景的原因。
顺便要提到的是,Flash虽然对GIF,JPG和PNG都有所支持,但是渲染速度上PNG还是占有绝对优势,所以建议flash中的位图都尽可能采用PNG
格式。

12、运用线条时,尽量减少和避免冗陈的线条结构,因为它们会直接影响到flash的播放效率。

13、当某个实例透明度小于1时,也会对播放速率造成影响。

14、尽量使用单独一个onEnterFrame事件,因为如果给过多的MoveClip添加这样的事件,很可能会导致“无头绪码(spaghetti code)”的出
现,并且容易导致程序效率明显下降。

15、用乘-1来代替Math.abs()方法。比如
var nn:Number = -23;
var test:Number= nn < 0 ? nn * -1 : nn;
要比
var nn:Number = -23;
var test:Number = Math.abs(nn);快。
当然还有更多的优化计算的方法。一般来说,低级运算要比高级运算速度;内部方法比调用其他方法速度快。
另外要注意的是,这些方法有的时候可能并一定适用。

16、用unit()或int()代替取整运算Math.floor()和Math.ceil()。比如
var test:uint = uint(1.5);
要比
var test:Number = Math.floor(1.5);快;
而var test:uint = uint(1.5)+1;
要比var test:Number = Math.ceil(1.5);也快。
如果是Math.floor(),还可以用位运算(>>0)来代替。比如
var test:uint =1.5>>0,比unit()或int()更快。

17、用位运算代替除2或乘2。比如10>>1要比10*2快,而10<<1要比10*2快。
从测试来看位运算几乎比乘除快一倍,但是一般情况下,我们不能选择位运算,比如我们就不能用13>>1来代替13/2,尽管前者比后者运算速
度更快,但2者的运算结果却不一样。所以还是要看具体情况。

18、用乘法来代替除法(当除数可转化为有限数的时候)。比如
var n:Number = value * 0.5;
要比
var n:Number = value / 2;快。
但差别并不是很大。只有在需要大量计算情况下,比如3D引擎中差别才比较明显。
19 将数组长度存储到一个变量中可以提高Flash的执行效率