【转】Flash高性能开发基础系列—内存篇

转自:青竹的日志

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

合理使用对象

创建不同对象一般所消耗的内存是不一样的。如: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,而使用现有的对象。这些做还有个好处就是,节省了创建对象的性能开销.

这里是不断的创建对象

1var size:Rectangle;
2var bitmap:BitmapData=new BitmapData(100,100);
3for (var:int = 0; i < 100; i++)
4{
5
6    size = new Rectangle(i,0,1,10);
7     myBitmapData.fillRect(size,COLOR);
8}

这是重用对象

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

对象池技术

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

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

 1package
 2{
 3    public interface IRecyclable
 4    {
 5       function dispose():void;
 6     }
 7}
 8//对象池的实现
 9package{
10public class ObjectPool{
11
12        private var pool:Vector.;
13        private var type:Class;//回收对象的类型
14
15       public function ObjectPool(type:Class) 
16       {
17          this.type=type;
18            pool=new Vector.();
19        }
20        public function addObject(o:IRecyclable):void{
21            o.dispose();
22            pool.push(o);
23        }
24         public function getObject():*{
25            if(pool.length<=0) return new type();
26             return pool.pop();
27          }
28    }
29}

对象的另类存储

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

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

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

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

 1package{
 2    import flash.geom.Point;
 3
 4     public class PointContainer{
 5
 6      private var container:Array
 7        public function PointContainer()
 8        {
 9            container=[];
10         }
11        public function add(v:Point):void{
12            container.push(v.x);
13            container.push(v.y);
14        }
15        public function getPointAt(index:int):Point{
16           return new Point(container[index],container[index+1]);
17        }
18    }
19}
20//使用
21var p:PointContainer=new PointContainer
22for(var i:int=0;i<1000000;i++){
23   p.add(new Point(i,i))
24}
25for(var i:int=0;i<1000000;i++){
26   p.getPointAt(i);
27}

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

事件优化

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

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

位图优化

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

  1. 将滤镜应用于显示对象时,Flash Player 将在内存中创建两个位图,所以这需要大量内存。所以尽量不要去使用滤镜,一般可以用ps做好滤镜后生成位图给flash来使用.
  2. 合理的使用位图缓存.对矢量图形做位图缓存,其实在把矢量图形变成位图,并使用该位图进行呈现,此会显著提高呈现的性能,但需要占用大量内存。针对复杂的矢量内容使用位图缓存功能。

释放对象

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

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

 1package
 2{
 3    import flash.events.EventDispatcher;
 4    import flash.events.IEventDispatcher;
 5
 6    public class MyEventDispatcher extends EventDispatcher{
 7        private var events:Array
 8        public function MyEventDispatcher(target:IEventDispatcher=null)
 9        {
10            super(target);
11            events=[];
12         }
13        override public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void{
14
15            events.push({type:type,fun:listener})
16            super.addEventListener(type, listener, useCapture, priority, useWeakReference);
17        }
18        override public function removeEventListener (type:String, listener:Function, useCapture:Boolean = false):void{
19               super. removeEventListener(type,listener, useCapture);
20              for(var i:int=0;i< ;i++){
21                  if( events[i].type==type && events[i].fun==listenner){
22                   events.splice(i,1)
23               }
24            }
25      }
26        public function dispose():void{
27          var ev:Object;
28            while(events.length){
29               ev=events.pop(); 
30                super.removeEventListener(ev.type,ev.fun);
31            }
32        }
33    }
34}

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

还有很多方法可以做到这一点,以上方法只是一个抛砖引玉.

滥用强制垃圾回收

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

1function gc():void{
2         try{
3                (new LocalConnection).connect("foo");
4                 (new LocalConnection).connect("foo");
5           }catch(e){
6                    trace(System.totalMemory);
7                }
8}

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

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占用的多少与计算机的配置也是有很大的关系的