您的位置:首页 > 其它

大型Flash项目性能优化:关注0ms方法

2012-10-29 19:37 337 查看
注:搞网页游戏两年,两耳不闻窗外事(只是偷偷上微博),居然没了整理的习惯。是时候好好整理和总结了。

Flash大型项目尤其是多人同时在线的webgame,性能优化是一个很重要的部分。性能优化大概就是两个部分:内存优化、CPU优化。所谓CPU优化,说白了就是降低CPU使用率。我们首先要解决的一个问题是:让代码不要做当前不需要做的事。

最近我们的项目(MMOARPG)加东西比较多,从Windows资源管理器看CPU,即使站在场景中什么都不干,CPU使用率竟然还在20%+。所以描述一下问题就是:


问题描述:找出让CPU空转的Function。

于是去查那些帧循环里的方法,看看是不是耗时太长。一般耗时2ms以上的,我们都会想办法优化。

场景中有NPC和怪物,我们要查一下他们的定时器:
EnterFrameManager.getInstance(ANIMAL_TIMER).add(frameScript);

private function frameScript():void
{
var start:int = getTimer();

// something may cost time
// code

var cost = getTimer - start;
trace("[$ms] frameScript : Animal".replace("$", cost));
}


结果偶尔打出来一些:
[0ms] frameScript : Animal
[1ms] frameScript : Animal


看上去不是这方面的问题。过了几天,在厕所里抽着烟,猛然悔悟了一下,立刻回去查一下EnterFrameManager。顺带说一下,当我们有很多帧循环或者Timer事件要注册,通常写一个FrameManager或TimerManager来做。在Manager里统一响应Event.ENTER_FRAME,处理绑定的所有事件。EnterFrameManager这部分简化后的代码如下:
public function add(script:Function):void
{
var o:Object = {"func": script};  // you may want to add some other attributes
scripts[script] = o;
}
private function enterFrameHandler(e:Event):void
{
for each (var i:Object in scripts)
{
i.func();
}
}


我们给超过30ms的enterFrameHandler加上执行时间,看看一帧那么宝贵的时间里,耗时的部分在哪。
private function enterFrameHandler(e:Event):void
{
var s:int = flash.utils.getTimer();
for each (var i:Object in scripts)
{
i.func();
}
var cost:int = flash.utils.getTimer() - s;
if (cost > 30) {
trace("[$ms] enterFrameHandler".replace("$", cost));
}
}


果然发现一些超过30ms的帧循环:
[32ms] enterFrameHandler


然后我们加一些代码看看这些帧循环里都搞了哪些破事儿:
public function add(script:Function):void
{
var o:Object = {"func": script};  // you may want to add some other attributes
o.name = getFunctionName(script);
scripts[script] = o;
}
private function enterFrameHandler(e:Event):void
{
var s:int = flash.utils.getTimer();
for each (var i:Object in scripts)
{
var ss:int = flash.utils.getTimer();
i.func();
i.cost = flash.utils.getTimer() - ss;
}
var cost:int = flash.utils.getTimer() - s;
if (cost > 30) {
trace("[$ms] enterFrameHandler".replace("$", cost));
for each (var i:Object in scripts) {
trace("[$ms] ".replace("$", i.cost) + i.name);
}
}
}


输出的结果让人吃惊,32ms的enterFrameHandler里,居然有大量0ms的方法,超过40个:
[32ms] enterFrameHandler
[0ms] frameScript
[0ms] frameScript
[0ms] frameScript
[1ms] frameScript
[0ms] frameScript
[0ms] frameScript
....省略
[0ms] frameScript


这是什么意思呢?就是说,很多执行了0ms的方法,加起来的时间超过了30ms。经查,有很多NPC和场景特效,虽然出了视野,但是帧循环没有移除。当主角看不到这些NPC和场景特效的时候,这些循环还在跑。虽然执行时间很短(不超过1ms),但是加起来不容忽视。0ms方法加起来不是0ms!一帧的时间也就40来毫秒(24fps),这些空循环偶尔就占了30ms,难怪帧频会降低,难怪会占CPU,失误啊。

解决办法:果断在NPC和场景特效出视野的时候,移除帧循环frameScript,问题解决。


结论

在大型项目中,不要忽视了帧循环里的看上去不那么耗时间的函数,他们的执行时间可能只有0ms,但这只是因为我们测不准小于1ms的时间。即使你说,我这些方法里什么都没干,他们也可能是因为函数调用栈深度问题而消耗了时间。当有很多这样的方法时,它们众志成城,团结的力量就会给CPU带来压力了。我们应该去掉那些没用的帧循环,需要的时候再加上。


延伸

这次比较幸运,出视野之后帧循环还可以清掉。但如果很多玩意儿都在视野里呢?CPU是省不来的。想让玩家获得最好的体验,我们应该做的是让CPU保持稳定,也就是让帧频保持稳定。我认为,换句话说,在一个帧循环里不要干太多事儿。这个怎样在策略上优化呢,回头再总结这方面经验。

付上网上找的获得方法名称的函数:
private function getFunctionName(fun:Function):String{
try{
var k:Sprite = Sprite(fun);
}catch(err:Error){
var fn:String = err.message.replace(/.+::(\w+\/\w+)\(\)\}\@.+/,"$1");
return fn==err.message?(err.message.replace(/.+ (function\-\d+) .+/i,"$1")):fn;
}
return null;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: