通过Adobe Scout深入理解Flash播放器
原文:Understanding Flash Player with Adobe Scout
作者:Mark Shepherd
作者:Michael Smith
转载请注明出处
Adobe Scout 是优化Flash内容异常强大的工具,因为他可以让你看到在Flash播放器里场景背后真正发生了什么.但只有当你理解了Flash播放器为什么那么做的时候那些信息才是最有用的.也只有那时候你才能有效地解决Scout告诉你的问题.
这篇文章的目的是让你对Flash播放器如何工作有一个基础的理解,并且理解与此相关的在Scout里看到的信息.这也是Scout中用到的terminology的一个指南,因此你可以找到Flash播放器执行的不同的活动的意义.如果你用过Scout并且有"我发现我的程序花了很多时间在做X上,但我不知道X是什么意思"的疑问的话,这篇文章正适合你!
注意这不是一个使用Scout界面的指南.如果你不知道如何使用Scout,或者不同的面板干什么,那么你需要读一下开始指南.
Flash播放器总揽
Flash播放器就像一个替你管理复杂事务的个人助理,充当你和现实世界中间的一个接口.但是Flash播放器自己不能做任何有用的事情-它需要你告诉它做什么!有两种方式告诉它:
- 时间轴,在Flash Professional里编辑.他们是基于帧的动画,通过一系列的tags展现.Tags是一种描述在每一帧中做什么事情的简单指令,比如在屏幕中移动物体.所有你在Flash Professional里做的可见动作,像添加一个物体到帧上或者创建一个变形动画,都会作为一个tag编译进SWF文件中.
- 用纯Action Script写.这些会被Flash播放器在确定的时间及时执行,例如当一个SWF被加载了,一个定时器触发了,或者点击了一次鼠标.你也可以在Flash Professional里关联脚本到不同的帧上.
当Flash播放器执行你的脚本,或者你的时间轴上的tags时,他们会告诉Flash播放器去执行很多不同的activities.基本上可以把他们定义为四种类型:
- 立即activities.这些是你告诉Flash播放器直接做的任务,并且直到指令完成才会继续执行下去;例如,创建一个新的位图或者初始化一个HTTP请求.
- 持续activities.当你初始化完这些任务后,Flash播放器会继续在后台执行他们直到完成或者你结束他们.例如播放一个声音或者下载一个文件.
- 延迟activities. 有些在脚本或者tag里执行的操作并不会立即去执行, 而是让Flash播放器稍后安排一个更大的操作.例如,改变一个显示对象的位置会标记它为"脏"了,意味着过一段时间后它就会被Flash播放器重新绘制.
- 隐式activities.这些是一些不需要你请求而由Flash播放器做的内建的操作,像垃圾回收,或者从操作系统接收鼠标键盘事件.
在你读这篇文章的时候认识到下面这一点是很重要的:Flash播放器不仅仅是执行ActionScript;它还做了很多其他的activities!把Flash播放器想象成你的个人助理,那么执行Action Script就像它在开会.即使是一个很短的会议,Flash播放器也会在开完之后离开去做所有你给的文书工作.渲染精美的动画需要时间,还有很多在你的脚本之外发生的事情.
播放器实例和会话
你也许想知道Flash播放器如何管理所有这些不同的activities的执行.
当你在浏览器中运行Flash内容时,Flash播放器通常会在一个独立的操作系统进程中运行. 在那个浏览器中的所有SWF共享一个进程, 包括所有相关的worker线程,并且Flash播放器会管理那些SWF的执行使他们看起来像是独立运行.每一个运行中的SWF是一个播放器实例,并对应着一个在Scout里的会话.Worker线程也是播放器实例.对于AIR内容,只有一个主播放器实例加上它用到的workers.
一个播放器实例的执行
当Flash播放器运行时,它会在不同的activities之间切换.有可能它执行一点脚本,然后播放一些视频,然后渲染一些图片,诸如此类.确切的组合依赖于发生了什么事件,哪些持续activities有了进展,以及SWF的脚本和时间轴请求了什么activities.当所有的这些在进行中,Flash播放器会保存一份它做的事情的记录,并且衡量它消耗的资源,包括CPU时间,CPU内存和GPU内存.这些衡量指标发送给Scout,因此你可以看到正在发生什么.为了保证测量这些因素的系统开销最小,Flash播放器只测量和报告那些非常费时间或者内存的activities.
在最基础的层面,Flash播放器在任意时间可以是两个模型中的一个:
- 活动-它在执行一些作业,可能是从执行脚本到渲染到捕捉鼠标事件中的任何事情.
- 不活动-它在茶歇(声明:不要给Flash播放器加水).它可能被一些外部的情况阻塞了,像GPU或者它只是把所有的工作都做完了.
在Scout里,全部时间(活动和不活动时间的和)用灰色条标识在帧时间轴上.为了更简单地可视化Flash播放器花在不同类型activity上的比例,Scout把活动时间分成四种顶级类型:
- ActionScirpt(蓝色)-花在运行ActionScript代码上的时间,包括内部ActionScript的API.
- 显示列表渲染(绿色)-花在执行显示列表的操作上的时间,像栅格化和画到屏幕上.注意一点就是这部分不包括使用了不同ActionScript API的Stage3D的渲染.
- 网络和视频(黄色)-花在通过网络下载,流化和解码视频上的时间.
- 其他(橘色)-花在其他方面的时间,比如垃圾回收,处理事件和解析SWF.
理解Scout展示的流逝时间和非CPU时间是非常重要的.把Flash想象成拿着秒表坐在那儿,掌握每一个activity.如果操作系统在activity的中间打断了Flash播放器,让其他的应用程序运行了一会儿,那么Flash播放器会测量到一个长一点流逝时间.为了得倒更精确的数据,在用Scout做性能测试时尽量关闭其他程序.
Flash播放器的心跳机制
许多Flash应用,尤其是游戏,在图形动画方面做了非常好的处理.基本的原则就是用固定的频率重画屏幕--例如60帧每秒-这样动画看起来就很平滑.为了在这种类型的编程方面帮助你,Flash播放器有一个核心的内容:frames.深入播放器的内部,有一个心跳机制-一个被称作帧速的按照固定频率触发的定时器.心跳之间的时间被称作:帧.你需要确定你希望你的内容运行在什么帧速上.它是一个SWF的属性,你也可以通过脚本在SWF运行时动态的改变它.
常用的编程方式是注册一个对Event.ENTER_FRAME的监听,这个事件与心跳一致并且发生在新的一帧的开始.如果你正在写游戏,你可以在这个监听里做所有维持游戏运行的周期性工作.例如,你可以处理所有用户的输入,用物理引擎更新游戏状态,最后渲染更新了的场景.
当你用Scout进行性能测试时,帧时间轴显示的是Flash播放器花在执行每一帧上的时间.假设一个游戏你想达到60帧每秒(60fps).每秒有1000毫秒(ms),所以每一帧有1000/60 = 16.7ms的预期.如果平均每一帧的时间多于这个数,那么Flash播放器将不能运行在你期望的帧速上,你的内容看起来就会很慢和跳帧.在Scout的帧时间轴,一条红色的水平线显示了个这个预期,灰色的条显示了每一帧花费的全部时间.
在每一帧中,Flash播放器需要执行很多不同的activities.最先开始做的事情中的一个就是派发一个Event.ENTER_FRAME事件和跳用所有注册的监听.在执行你的脚本代码时,会造成新的deferred或者和持续activities被加入“待做列表”,像重绘部分屏幕或者开始下载一个新的文件.这些只会在一帧中稍后时间执行到.同样的,鼠标,键盘,和其他的事件可能会在一帧的中间发生,这些也需要被处理和捕获到.最终Flash播放器会完成一帧中所有的任务.接着它会等待下一次心跳,然后随着新的一帧再来一遍.
在Scout里,需要关注的主要事情是超过红线的彩条.意味着Flash播放器花了比整帧预期还要长的时间去做所有需要做的事情.用另一句话说,Flash播放器在它分配的时间里做了太多的事情!如果所有的帧都超出了预期,一般来说你的内容就会很慢(查看图1),如果有很多不规律的尖峰,那么你的内容会跳帧.
图1. 内容持续低于目标帧速
需要注意的一点是即使在很段的时间内完成了一帧,Flash播放器总是会在开始下一帧之前等待着.这是一个好事-意味着你的东西不会霸占着设备上所有的资源,这样会减少电池消耗并且让CPU执行其他的任务.同时意味着你的东西有一顶的余量,可以在一些低端的设备上达到目标帧速.所以灰色条一直在红线上下是完全正常的(查看图2).
图2. 内容流畅的运行在60fps
还有一些其他的关于帧的小事情需要关注:
- Flash播放器的最高帧速被限定在60fps.设置更高的帧速也不会让你的内容超出它.要知道大部分的显示器最高支持60fps,尝试比这还快的更新屏幕是一个大大的NO!
- Flash播放器尝试与操作系统友好相处.当你浏览网页时可能会有很多播放器实例在运行,并且每一个帧速都不一样.为了不随机的唤醒和拖慢你的电脑,Flash播放器尝试同步不同播放器实力的唤醒时间.大体上讲,它每隔1/60秒唤醒,然后在所有准备好的播放器实例里开始新的一帧.这意味着如果你设置了一个不能被60整除的帧速,例如24fps,那么你会看到灰色条在红线上下震荡-又是搞高,有时候低(查看图3).这也是正常的,只要平均达到了目标帧速就不是什么问题(你可以在Scout里选择一定范围帧查看平均帧速).
- 如果你在Scout里看到灰色条一直高于红线,但彩色条(活动时间)却一直低于它,那么有可能是别的运行在电脑上的应用程序用了过多的CPU资源.尝试关闭其他程序和浏览器窗口.
图3. 内容流畅的运行在24fps
Flash播放器的组件
现在你对Flash播放器的基础架构有了一个很好的理解,可以对在Scout里看到的数据进行更深入的研究了.你可能注意到Top Activities和Activity Sequence面板提供了更详细的关于Flash播放器做了什么的分解.这些面板的描述设计成尽可能的能够自解释,但有时候你需要多一点信息才能找到问题的根源.这一章节把Flash播放器的不同activities按照功能分组(查看图4),并且提供了关于他们的意义的更细节的解释.
图4. Flash播放器的架构
你可以把这一章节当作使用Scout时的指南,并且只阅读与你的问题相关的部分.或者也可以阅读全部以更多的了解Flash播放器各个部分如何工作.
ActionScript 3和事件处理
ActionScript是Flash的语言;你通过它告诉Flash播放器干什么.当你创建一个SWF,你的ActionScript代码会被编译成一个称作ActionScript字节码的Flash播放器可以理解的低级语言.当你的SWF运行时,这些字节码会被ActionScript虚拟机(AVM)执行.AVM实现了一些ActionScript的核心功能,包括垃圾回收和异常处理,就像你的代码和Flash播放器之间的桥梁.
你可以通过API用ActionScript和Flash播放器交互.对你而言看起来像普通的函数调用,但实际上你正在调用的是原生C代码,就像魔术一样!在Scout里,这些API当作普通函数调用显示在ActionScript面板里,所以你不用担心内部的细节.如果你在一个API调用上画了很长时间,也许应该少调用它或者让它做更少的事情(减少参数的大小或者复杂度).
与从ActionScript中调用Flash播放器一样,你需要告诉Flash播放器什么时候执行你的代码.大部分时候,这个靠事件捕获完成.他们是一些注册在特定事件发生时调用的方法.你可以创建自定义的事件,然后通过EventDispatcher.dispatch()调用它们,但你也经常希望监听外部事件,像鼠标移动和按键.在Scout有一部分activities是关于这个的:
- 处理事件""-Flash播放器会一直从操作系统监听和接收外部事件.当它接收到一个事件,首先它会做一些处理,然后找出需要调用哪些事件捕获(例如,有一个键盘事件,被调用的捕获依赖与那个对象有键盘焦点).在Scout里看到的外部事件有:
- 键盘事件:Keyboard events: keyDown, keyPress, keyUp.
- 鼠标事件: mouseDown, rightMouseDown, mouseMove, mouseUp, mouseWheel.
- 触摸和手势事件: touch, gesture.
- 窗口事件: resize, mouseLeave, foreground, background, fullScreen, fullScreenInteractiveAccepted, visible.
- 事件""-这是实际上事件 的ActionScript事件捕获.你只会在创建并注册了一个事件捕获的时候看到它(这个应用于任意事件,不仅仅是上面列出那些).这儿有一个妙招,在Scout的Activity Sequence面板或者Top Activities面板上点击一个事件在ActionScript面板里就会只显示执行这个事件捕获的代码.
定时器跟外部事件一样处理,除非你自己设置它们.
- 捕获"timer"事件-派发一个TimerEvent.TIMER事件到所有的注册捕获上.
- 定时器开始:-标识一个有毫秒的定时器开始了.
还有一些跟执行代码相关和支持ActionScript语言特性的AVM activities:
- 垃圾回收-不像C和其他需要显式内存管理的语言,ActionScript代替你负责申请和释放内存.Flash播放器会周期地运行垃圾回收器去扫描所有不再被引用的对象(没有东西引用它们),并且回收它们用到的内存.如果你的应用在垃圾回收上花了大量的时间,表明你创建了太多的对象.也许需要缓存或者池化对象来避免一个创建新的实例.
- 输出:-这给出了在ActionScript里所有的trace()输出.你也可以在Trace Log面板查看它们.
- 异常-这给出了所有在运行ActionScript时发生的异常的堆栈.无论异常被截获和捕获都会显示出来,所以在Scout里看到这个并不意味着你的代码有问题.
- 处理未截获的异常-派发一个UncaughtErrorEvent.UNCAUGHT_ERROR到所有注册的捕获上.
最后,还有一些在你从其他语言调用ActionScript3的时候看到的activities:
- AVM Bridge callback – 从ActionScript 2调用ActionScript 3代码.
- ExternalInterface callback – 从JavaScript调用ActionScript 3代码.
ActionScript workers
如果你在应用中使用了ActionScript workers,他们在Scout里会显示在独立的会话中.在后台,他们真的是独立的播放器实例,他们之间使用一个API来分享数据.对workers的支持现在在Scout里是一个测试功能.需要做以下的事情来开始这项支持:
- 在Scout里,点击选项>测试功能,选择开始ActionScript Workers的会话.
- 在Scout的侧边栏(如果不可见,点击窗口>侧边栏),确保ActionScript Sampler是开启的.
- 确保你的应用使用advanced telemetry选项编译(如果你不知道如何做,查看http://www.adobe.com/devnet/scout/articles/adobe-scout-getting-started.html).
对于使用了ActionScript workers的应用,你可能会在Scout看到以下activities:
- 运行中的worker代码-如果你加载了一个worker,并且在它里面持续的运行一些ActionScript(worker执行一个长运行的方法,而不是靠事件驱动),你会在Scout里看到这个activity.它意味着你仍然在worker的高级入口,因此它不对应着任何事件.
- 等待条件-被一个Condition.wait()的调用阻塞了.
- MessageChannel.receive-被一个MessageChannel.receive()的调用阻塞了.
- MessageChannel.send-被一个MessageChannel.send()的调用阻塞了.
- Mutex.lock-被一个Mutex.lock()的调用阻塞了.
- Mutex.trylock-被一个Mutex.trylock()的调用阻塞了.
用户activities
除了内建的Flash播放器的测量因素,你也可以使用Telemetry API发送自定义的数据给Scout.主要有两个调用:
- Telemetry.sendMetric(name, value)-这个报告一个名称-值对给Scout,将会显示在Activity Sequence面板.例如,如果你调用Telemetry.sendMetric("UserID", 2),当这段代码执行的时候你会在Activity Sequence面板看到"UserID: 2".
- Telemetry.sendSpanMetric(name, startTime, value)-这个会报告一个带可选值得activity到Scout.你通过查看Telemetry.spanMarker记录开始时间,然后在想要测量的最后时间点把它传给Telemetry.sendSpanMetric.它会显示在Scout的Top Activities和Activity Sequence面板里.
关于更多的细节和使用实例,参见:http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/profiler/Telemetry.html
Frame ticker
Flash播放器的核心是frame ticker-每到新的一帧开始时脉动一次的心跳机制,它执行所有的时间轴tags,调用时间轴上所有的帧脚本,并且派发一部分ActionScript 3的关键事件.frame ticker的activities如下:
- 运行帧上的SWF tags-Flash播放器执行与帧相关的SWF tags.通常与你在Flash Professional里做的事情有关;例如,创建你附加在帧上的对象,通过你的tweens移动它们.
- 运行帧动作-这些是通过在Flash Professional的脚本面板附加到时间轴上某一帧上的脚本.脚本可以是ActionScript 2或者ActionScript 3.如果是ActionScript 3,那么它可能不会被立即执行,而是放到一个Flash播放器在帧结束前执行的一个队列里.
- AS2 的事件"enterFrame"-ActionScript 2里的MovieClip.onEnterFrame事件捕获
- 捕获事件"enterFrame"-当新的一帧开始时,Flash播放器找到所有对Event.ENTER_FRAME注册的捕获,并执行他们.
- 捕获事件"frameLabel"-当进入了新的一帧时,如果这一帧有一个注册了Event.FRAME_LABEL捕获的FrameLabel对象,那么它的捕获会被执行.
- 捕获事件"exitFrame"-在当前帧结束时,Flash播放器会查找所有对Event.EXIT_FRAME的捕获,并执行它们.
显示列表渲染
在Flash播放器里显示列表是经典的渲染方法.总的来说,你被给予一个叫做stage的空白画布,你可以通过附加和布置称为显示对象的图形实体来画它.有许多不同类型的显示对象,包括向量图,位图,文本,他们可以分级的嵌套来创建一个复杂的场景.无论你用ActionScript和显示列表交互或者在Flash Pro里配置和设置变形动画,都不用担心它们如何被绘制.Flash播放器为你做了大量的工作去计算如何把显示列表转化成你在屏幕上看到的真实像素.
如果你用ActionScript操纵显示列表,你会知道有一些可以产生改变的API.你可以把这些改变想象成立即activities.当你调用一个API,它直接改变了显示列表的内部状态.但它并没有立即改变你在屏幕上看到的东西!相反的,Flash播放器会标记所有你改变的现实对象为脏,以为这他们需要被重新绘制.一段时间以后,你的代码运行完了,Flash播放器会执行一个绘制过程.事实上,它把所有的改动收集到一起,然后只更新一次屏幕.你可以把这想象成逐格动画.在一帧里,把所有小的泥人都调整好,然后当你完成了,Flash播放器会给那一帧的场景拍一张照.
图5. 一次Flash播放器的渲染过程
这儿是在Scout看渲染过程的样子(查看图5):
- 计算脏矩形-渲染周期从扫描全部的显示列表,找出所有的标记为脏的显示对象开始(例如,他们被移动了).然后生成包含所有脏对象的3个矩形.这些是需要重新绘制的屏幕区域.你可以在显示列表绘制面板看到那些红色区域.
- 绘制脏矩形-对于每一个脏矩形,Flash播放器需要计算每个像素的值.它通过在一个内部缓冲里创建场景来实现,被称为显示缓冲.在这个过程中引入了以下几步:
- 建立显示对象的边缘-Flash播放器再一次扫描整个显示列表,查找与脏矩形覆盖的显式对象.每发现一个,都会把它分解成一组边缘和填充(可能是色块,渐变,或者是定义边缘之间如何填色的位图).如果显示对象包含其他显示对象,会递归的执行相同的过程.从本质上讲,Flash播放器把你的场景树平面化成不同的填充区域.
- 栅格化边缘-Flash播放器现在要把边缘和颜色转化成真正的像素.它把脏矩形分解成很多的称为扫描线的水平线,每个垂直像素是一条线.扫描线在你的机器上的核心间是共享的.对于每一条扫描线,Flash播放器会基于每一个像素周围的边缘之间的区域的填充计算出每一个像素的颜色.花费的时间取决于像素的数量,和每一个像素的花费的时间.色块最容易画,旋转和缩放的并且有多层的alpha混合的位图是最耗时间的!
- 应用过滤器:-如果你设置了像发光或者阴影的过滤器,现在将应用到栅格化的图像上.它必须在父对象栅格化之前应用,除非有一个子显示对象有过滤器。Flash播放器会把子显示对象栅格化到独立的表面(用蓝色在Scout的显示列表渲染面板栗显示),并且把过滤器应用到那个表面.这样会创建一个新的在创建父显示对象边缘时作为原子填充对待的位图.
- 复制到屏幕-在渲染过程的最后,Flash播放器复制它的显示福村到屏幕上.也被称为展示.这儿需要的时间大部分取决于复制的像素数,但也有可能在向操作系统获取锁时存在延迟.如果你用了硬件加速,那么显示缓存已经在图形卡里了,Flash播放器只需要告诉图形卡显示一下.这部分时间会显示为等待GPU.如果没有,检查一下是不是用的最新版本的Flash Player(11.5 或者更新).
如果绘制带有很多边缘和不同过滤器的复杂形状的花,渲染是非常费的.尽可能少的修改显示列表会减少每一帧中需要重绘的脏矩形的数量和尺寸.如果要绘制一个复杂嵌套但是很少修改的对象(除了移动它),你可以通过缓存来提高性能.把显示对象栅格化到一个平面,然后只在改变的时候重新生成.如果你只会变换一个显示对象,设置属性cacheAsBitmap.如果你还想旋转和缩放的话,用cacheAsBitmapMatrix(只在移动端的AIR上支持).
Scout显示了一下与缓存平面相关的activities(用橘色在显示列表渲染面板里显示):
- 从缓存的平面渲染-如果一个显示对象有一个有效的缓存,Flash播放器将使用它而不会创建和栅格化他的边缘.
- 更新缓存的平面-如果修改了一个缓存的现实对象或者它的子,都会使缓存失效,需要重新创建.如果Flash播放器在更新缓存平面上花了很多时间,表明你是用的缓存不当.缓存应该只用在很少改变的显示对象上,否则还不如不缓存.
作为每一个渲染过程的一部分,Scout会显示在某些特定类型的显示对象上执行的activities的额外的信息.下面这些activities是渲染文本相关的:
- 更新文本布局-一个文本显式对象变化了(例如,改变了一个TextElement或者TextField对象的text属性),并且需要重新布局时.这同样会标记显示对象为脏,然后会在下一个渲染过程中重绘.
- 渲染文本-用TextField类渲染文本
- 渲染FTE文本-用Flash文本引擎渲染文本,在flash.text.engine包中.
下面是与位图对象相关以及操纵他们关联的bitmap data(Bitmap.bitmapData):
- 创建BitmapData-BitmapData对象的构造方法.只出现在Scout的现实列表渲染面板里.
- 解压图片-当加载一张压缩图片,比如JPEG或者PNG时,需要在Flash播放器显示它之前解压缩.如果你发现这部分花了很长时间,你可以告诉Flash播放器用异步解压,它会在加载完成以后用一个后台线程去解压缩图片,从而不会阻塞了UI.
- BitmapData.copyPixels-调用BitmapData.copyPixels().
- BitmapData.draw-调用BitmapData.draw().
还有几个你可能会在Scout里遇到的其他显示列表渲染相关的操作:
- 创建显示缓存-这儿创建了Flash播放器栅格化显示列表用的显示缓存.通常只会在播放器实例创建时发生一次.
- 栅格化显示缓存-这儿调整显示缓存的大小,通常是由于stage重新调整大小.
- Triggering UpdateAfterEvent-由于调用updateAfterEvent()而开始一个渲染过程.按照经验法则,如果你已经设置了一个很高的帧速(像30fps或者60fps),那你不需要调用它,因为Flash播放器已经每帧都会至少调用一次.
- Handling event "render"-如果显式调用stage.invalidate(),一个Event.RENDER事件会在渲染过程开始前被派发到所有的注册捕获上.
Stage3D渲染
Stage3D是一个高于OpenGL和DirectX的ActionScript API,用来实现跨平台的硬件加速渲染.虽然Starling框架运行在Stage3D上并且提供了类似于2D内容的显示列表的API,但是它的工作方式与传统的现实列表模型有很大的区别.Stage3D的内容渲染到它自己的显示列表下面的显示缓存上.
Stage3D渲染周期的基本结构是首先设置GPU的状态(上传贴图,骨骼和你想用的着色器),然后放出一系列的绘图调用告诉GPU在目标缓存上绘制一组三角形.当你创建完场景后,调用Context3D.present()来真正的把它显示到屏幕上.有两个地方产生了实际的工作:
- 在ActionScript API内部-通过flash.display3D包的调用.查看这部分的时间,需要在Scout里打开ActionScript Sampler.如果你在Summary面板打开ActionScript类型,你可以看到在Stage3D API上花了多长时间,ActionScript面板可以浏览那些具体调用时最慢的.
- On the GPU-基本上这是显卡做的大部分工作,像实际上的渲染.Scout现在不能提供任何关于GPU处理时间的信息,但是你可以在Summary面板看到GPU的内存使用.如果你认为你的瓶颈是GPU,查看下面的章节 GPU相关的性能问题.
SWF加载器
当Flash播放器开始一个新的播放器实例,首先它必须在开始执行主SWF文件之前下载它,解析它并把它加载到内存中.你可以在Scout里看到下面与此相关的activities(注意这部分和网络组件有重叠):
- 加载SWF:-加载的主SWF的URL.当运行独立版本的Flash播放器时会看到这个.
- 加载文件:-下载指定URL的文件.
- 接收加载器数据-接收用Loader.load()加载的网络资源的数据.每接收到一次数据,一个ProgressEvent.PROGRESS事件会派发到所有注册捕获上.
- 接收SWF数据-接收SWF文件的部分数据
- 接收图像数据-接收图像文件的部分数据,像JPEG,PNG或者GIF.
- 解码SWF文件-解析SWF数据,需要的话解压缩.
- 关闭加载器-关闭加载器使用的网络连接.可能是因为加载完成,或者发生了错误,抑或调用了Loader.close().
- 准备ActionScript字节码-解析AS字节码为可以执行的格式.
- 初始化AS globals-初始化定义在方法或者类定义之外的变量.
- 捕获"onLoadInit"事件-AS2 中的MovieClipLoader.onLoadInit事件
网络
Flash播放器主要支持三种网络操作:本地连接,TCP/HTTP连接和流媒体.无论你用哪一种,通常都会开始于设置一个网络连接或者初始化一个请求.通常网络操作需要时间,因此他们不会同步的发生.一旦你请求了一些事情,你的ActionScript代码会继续执行其他的任务,Flash播放器会在后台捕获网络操作.你可以通过注册相关的监听来知道什么时候操作完成了,或者状态更新了.
你可以设置一个LoacalConnection对象,让在同一台机器上的两个播放器实例(例如你加载了一个帮助SWF)通过调用对方的方法交流Scout会显示下面这些跟本地连接相关的activities:
- LocalConnection回调-在接收LocalConnection对象上调用functionName(arguments),抑或在发送LocalConnection对象上调用send(connectionName, functionName, arguments).
当你希望下载一些东西,比如图片或者其他SWF文件,通常会使用基于HTTP的Loader或者URLLoader对象.你也可以使用HTTP NetConnection从服务器发送和接收数据.Scout显示下面这些与此相关的activities:
- 发送URL请求-通过HTTP发送的请求,会被Flash播放器队列化处理,像NetConnection.call().这些队列化的请求会定期的通过网络发送出去.
- URL请求时间戳-URL请求发送的时间.
- URL请求:-Flash播放器请求的URL地址.
- URL请求ID:-URL请求的唯一标识.
- 响应回调-调用传给Responder对象的构造方法的函数,对应着NetConnection.call()的成功处理或者返回一个错误.
- 处理网络缓存-一般网络开销,比如便利缓存查看是否接收到数据
Flash播放器也支持一些服务器的流媒体协议,像音频和视频数据.你可以用NetConnection创建制定协议的NetStream.实时信息协议(RTMP)基于TCP协议,实时媒体流协议(RTMFP)基于UDP协议.在Scout里可以看到以下与流相关的activities:
- 接收NetStream音频/视频-从服务器接收一组视频或者音频数据.
- 从网络解码媒体-把从网络上接收到的声音或者视频数据解压,然后Flash播放器才能播放他们
- 接收NetStream元数据-接收像创建时间,时长,类型以及其他请求媒体的元数据.你可以通过设置NetStream.client上的onMetaData属性来注册一个接收到元数据的事件捕获.
- 接收NetStream命令-执行服务器的命令信息.例如告诉Flash播放器之前请求的命令的状态.
- 关闭网络连接-可能是流结束了,或者发生了错误,又或者调用了NetStream.close().
- 接收NetStream共享对象-接收一个存储在服务器上的AS3对象(这样就可以在不同的客户端之间共享数据).调用SharedObject.connet()会导致这个发生.
声音
在大部分桌面电脑上,Flash播放器播放声音是很有效率的.在Scout里花费在执行声音操作的时间作为调用在flash.media包里的方法显示在ActionScript面板里,比如Sound或者SoundChannel对象.你可能会看到下面的activity:
- 完成派发声音-AS2 里的Sound.onSoundComplete捕获,或者AS3里的SoundChannel对象的Event.SOUND_COMPLETE捕获.
视频
在Flash播放器里播放视频是一个长运行的网络操作.当你告诉Flash播放器你要播放一段视频,它会在后台启动一个持续activity.数据通过网络定时的到达,解压缩后,显示在屏幕上.CPU时间根据平台解码器和其他的视频设置而有很大的不同.
Scout现在不能提供很多关于视频性能的信息,但你可能会看到以下activity:
- 初始化StageVideo-设置StageVideo的对象.
杂项activities
还有一些其他不属于上面的分类的activities:
- 运行AS2-执行AS2代码.即使没有写任何AS2的代码,也有可能会出现这个,因为Flash播放器会为了执行特定的初始化和辅助功能自动生成一些AS2的代码.
- 启动AIR-在启动一个AIR程序时运行一些AS代码.
- 按钮碰撞检测-Flash播放器有一些特殊的代码去处理旧类型的按钮对象(在Flash Professional里创建的那种).与查找鼠标事件的AS事件捕获不同,当鼠标使用时它会在显示列表搜索所有这种类型的按钮.如果在显示列表里有很多的显示对象这会是非常耗费的.不幸的是,即使没有使用旧类型的按钮对象这个操作也会发生,Adobe正在修复这个问题.
- 运行时事务-这个包含了所有没有被其他acitivity计算的时间.Flash播放器只会测量那些通常会花费很多时间的acitivities,所有其他的小事情都会归属到这个类别里.你也不用担心这些微不足道的时间.
非活动时间
当Flash播放器等待一些事情时,在Scout里就会显示为非活动时间.有可能是因为它被某些事情阻塞了,也有可能是它做完了所有的事情.如果你在Scout的综述面板里打开非活动这一栏,你会看到它分成了下面中的部分或者全部:
- 等待下一帧-做完了当前帧的所有事情,所以Flash播放器会休眠直到下一帧,或者发生了一些外部事件(像鼠标或者网络事件).
- 等待GPU-Flash播放器在等待显卡执行某些操作.在GPU相关的性能问题一节会有更详细的信息.
- 等待条件-一个worker线程被Conditon.wait()或者MessageChannel.send()或者MessageChannel.receive()的调用阻塞了.
Scout的内存报告
在Flash播放器测量不同activities的执行时间的同时,Flash播放器也在追踪它用了多少内存.在Scout的综述面板可以看到这些,跟CPU时间一样,它也分成了不同的类型.Flash播放器只会追踪它自己显示分配的内存-一些操作系统分配的内存,比如存储Flash播放器的可执行文件,将不会显示在Scout里.
在Scout里察看一个会话使用的内存时,要意识到总内存是所有运行中的Flash播放器实例使用的内存之和.其他播放器实例使用的内存显示在其他分类的其他播放器下面.另外,有些有些在未分类里的内存也许是由别的播放器实例使用的.有些数据结构是不同的Flash播放器之间分享的,因此很难把它们归于与某个播放器实例.当你在浏览器里测试你的应用是,有可能的话最好只运行自己的SWF.
Flash播放器尽最大可能追踪用了很多内存的主要的数据结构,但有的地方会好一点,有的会差一点.现在,还不能测量音频或视频缓冲使用的内存,也不能测量JIT编译的AS代码.这些会跟其他没有追踪的内部数据结构一起显示在未分类里.
GPU相关的性能问题的注意事项
GPU是一个在显卡上能够让你硬件渲染特殊的处理器.它在做大量并行计算方面是非常强的,像给巨量的三角形定位和上色.CPU更好相反,可以做很多复杂的工作,但同时只能做好一件事情.Stage3D提供了一个AS API能让你直接操纵GPU.为了让生活更简单,你可以用一些高级的框架,比如2D应用使用Starling,3D应用使用Away3D.但是要记住,这些框架都是使用场景后面的GPU.
使用Stage3D时要注意的事情是GPU和CPU是并行运行的.这意味着你的应用被限制在最慢的部件上.即使你的AS是很快的(CPU没有过载),但是你给了GPU太多的工作时也不会达到你想要的帧速.这里有两个使用GPU时主要的共同性能问题:
- 可能让GPU做太多的工作.一般是由于使用了太多的绘画调用,太频繁的改变GPU的状态,或者过大的网格和纹理造成的.有很多的方法来优化你的应用.比如,使用压缩纹理,使用Starling的纹理集,也可以致使简单的简化渲染得物体.现在,Scout不能显示GPU正在做什么,但是你可以在Stage3D渲染面板里看到正在执行的Stage3D命令,然后想办法优化它们.
- 你可能尝试比GPU能达到的还要快的速度更新屏幕.显卡使用了双缓冲的技术.一个缓存保存了正显示在屏幕上的数据,另一个(后台缓存)用来创建下一个要显示的图形.当调用Context3D.present()时,就是告诉GPU显示你画的东西,意味着GPU将会转换着这两个缓存.但GPU只会1/60秒转换一次.这被称作垂直同步(Vsync),并且跟显示器更新图像的速度有关.如果GPU没有准备好转换,那么它会阻塞,在Scout显示为等待GPU.通常这不是问题,但如果CPU不能跟上垂直同步的频率,那么就有可能错过最后时机而一直等待下一次GPU更新的垂直同步.如果发生了这中问题,你可以通过设置一个较低的帧速来避免额外的等待时间从而提高性能.
有一个很有用窍门:如果你的应用在Scout显示有大量的等待GPU时间,尝试在Flash播放器里临时关闭硬件加速(点击右键选择设置).这会让Flash播放器使用软件渲染,就没有等待时间了,然后就可以确认你的问题是不是与GPU有关.
下一站
现在你知道Flash播放器如何工作了,你可以更好的用Scout优化你的应用.记住,如果在用Scout时忘了某个东西的意思你可以参考这篇文章.