首页 > 代码库 > Adobe Scout 使用参考说明
Adobe Scout 使用参考说明
Adobe Scout 用于优化 Flash 内容,是一款极为强大的工具,因为它能让您看到 Flash Player 幕后正在发生的事情。但是若明白 Flash Player 为什么做这些事情,您看到这些事情才会最有用。只有这样,您才能有效地找到修复 Scout 所告知的问题的方法!
本文的目的是让您大致了解 Flash Player 的工作原理,并将之与您在 Scout 中看到的信息相关联。本文也是 Scout 中所用术语的参考指南,因此您可以从中查找 Flash Player 执行的各种活动的含义。如果您使用过 Scout,并且认为“我花了太多的时间做某件事,却不知道某件事的含义”,那么本文就是为您准备的!
注意,本文不是 Scout 用户界面指南。不知道如何使用 Scout,或者不知道各个面板的作用,那么请首先阅读入门指南。
Flash Player 简介
Flash Player 像个人助理一样,帮助您安排复杂的事情,并充当您与外界的接口。但是 Flash Player 自己不会做任何事情,除非您告诉它做什么!有两种方式做这件事:
- 时间轴,用 Flash Professional 编写。这些时间轴是一帧一帧的动画,表示为一系列标记。标记就是一些简单的指令,描述在每一帧动画中做什么事情,比如说在屏幕上移动对象。您在 Flash Professional 中可以做到的每件事情,比如说向帧中添加对象或者设置一个补间,都编码为 SWF 文件中的一个标记。
- 脚本,用纯 ActionScript 编写。这些脚本由 Flash Player 在某些特定的时间点来执行,例如在加载 SWF 时、定时器触发时或者单击鼠标时。您也可以在 Flash Professional 中将脚本附加到单个帧中。
Flash Player 执行您的脚本或者时间轴中的标记时,这些脚本或标记会告诉它去执行各种活动。这些活动可以大致分为 4 类:
- 即时活动。这是您告诉 Flash Player 马上去完成的任务,此时,您的内容将停止执行,直到此操作完成才继续;例如,创建一个新的位图或者发出一个 HTTP 请求。
- 持续活动。在您发起这些任务之后,Flash Player 将在后台持续执行它们,直到完成或者您终止它们。例如,播放音乐或下载文件。
- 延迟活动。您在脚本或标记中执行的一些似乎不太重要的操作,但是它们会导致 Flash Player 稍后安排一个更大的操作。例如,更改某个显示对象的位置会将该对象标记为“脏的”,表示它稍后还将由 Flash Player 重新呈现。
- 隐式活动。这是内置的操作,无需您的请求,Flash Player 会自动执行它们,比如说垃圾收集,或者从操作系统接收鼠标和键盘事件。
在阅读本文时,一定要记住以下内容。Flash Player 不只是执行 ActionScript 代码,它还执行很多其他活动!如果您将 Flash Player 看作个人助理,那么执行 ActionScript 代码就像助理在会议中的工作。即使是一个非常简短的会议,Flash Player 仍然必须提前准备您需要的所有资料。呈现您的精美动画需要花费时间,大部分真正的工作发生在 ActionScript 代码之外!
播放器实例和会话
您可能会感到好奇,Flash Player 怎么管理这些不同活动的执行呢?
您在 Web 浏览器中运行 Flash 内容时,Flash Player 通常运行在一个单独的操作系统进程中。对于运行在该浏览器中的所有 SWF(包括任何相关的工人线程),只存在一个进程,所以 Flash Player 管理这些 SWF 的执行,就像它们都是独立运行的一样。每个正在运行的 SWF 称作一个播放器实例,对应于 Scout 中的一个会话。工人线程也是播放器实例。对于 AIR 内容,只存在一个主播放器实例,加上它所使用的任何工人线程。
播放器实例的执行
运行的时候,Flash Player 会在不同的活动之间切换。它可能执行一会儿脚本,然后播放一会儿视频,然后再呈现一些图片,等等。具体的顺序取决于正在发生什么样的事件、正在执行哪些持续活动,以及 SWF 的脚本和时间轴请求了哪些活动。在所有这一切的发生过程中,Flash Player 会记录它所做的事情,并测量消耗的资源,包括 CPU 时间、CPU 内存和 GPU 内存。这些测量值被发送到 Scout,以便您可以看到正在发生的情况。为了最小化这些测量的开销,Flash Player 只测量和报告那些花费时间或内存较多的活动。
基本上,Flash Player 在任何时间都处于两种模式之一:
- 活跃 – 它正在做生产性事情,可以是执行 ActionScript、呈现图片、处理鼠标事件等。
- 不活跃 – 它在喝茶休息(免责声明:千万别将茶水倒到 Flash Player 上)。这可能是因一些外部条件(如 GPU)而阻塞了,也可能是把所有的工作都干完了。
在 Scout 中,总时间(活跃和不活跃时间之和)用 Frame Timeline 中的灰色竖条表示。为了更加形象地表示 Flash Player 花费在执行各种不同类型活动上的时间比例,Scout 将活跃时间分为 4 大类别:
- ActionScript(蓝色) – 在 ActionScript API 中执行 ActionScript 代码花费的时间。
- DisplayList Rendering(绿色)– 执行显示列表上的操作(比如栅格化和拖曳屏幕)花费的时间。注意,这不包括 Stage3D 呈现,Stage3D 呈现使用的是一个不同的 ActionScript API。
- Network and Video(黄色)– 通过网络下载以及流线化和解码视频花费的时间。
- Other(橙色)– 做所有其他事情花费的时间,比如垃圾收集、处理事件和解析 SWF。
一定要知道,Scout 展示的是已逝时间,而不是 CPU 时间。可以把 Flash Player 想象成一个人拿着秒表坐在那里为每个活动计时。如果操作系统在活动中间打断 Flash Player,让另一个应用程序运行一会儿,那么 Flash Player 将测量到一个较长的已逝时间。为了得到最精确的数据,在使用 Scout 进行剖析时应该关闭其他应用程序。
Flash Player 的心脏
很多 Flash 应用程序,尤其是游戏,具有大量的图形动画。基本原理是以某个频率(比如说 60 次/秒)重绘屏幕,使之看起来像一个流畅的动画。为帮助您进行这种编程,Flash Player 具有一个核心概念——帧。进入播放器的深层,是它的心脏——一个以某种频率(称作帧速率)触发的定时器。这些心跳之间的时间称作帧。您需要确定以什么样的帧速率运行自己的内容。帧速率是 SWF 的一个属性,您可以在 SWF 运行期间从 ActionScript 动态地更改它。
一种常见的编程方式是,为 Event.ENTER_FRAME
(该事件对应于心跳和新帧的开始)注册一个事件处理程序。如果编写游戏,您可以使用这个事件处理程序来做保持游戏运行所需的所有周期性工作。例如,可以处理任何用户输入,使用物理引擎更新游戏状态,最后呈现更新后的场景。
使用 Scout 进行剖析时,Frame Timeline 显示 Flash Player 执行每一帧花费的时间。下面来考虑一款游戏,您想对它获得 60 帧/秒 (60fps) 的帧速率。一秒钟等于 1000 毫秒 (ms),所以每一帧的预算时间是 1000/60 = 16.7ms。通常,如果每一帧花费的时间都比这个时间长,那么 Flash Player 将不能以您所需的帧速率运行,您的内容就可能看起来缓慢而抖动。在 Scout 的 Frame Timeline 中,预算时间显示为一条水平红线,灰色竖条表示每个帧花费的总时间。
在一个帧中,Flash Player 要执行很多不同的活动。其中首先要做的一件事情就是分派一个 Event.ENTER_FRAME
事件并调用任何已注册的事件处理程序。在它执行 ActionScript 代码时,这可能会导致新的延迟活动或持续活动添加到“要执行的活动列表”,比如说重绘部分屏幕或者开始新的文件下载。这些活动必须在这个帧中稍后的某个时间得到执行。此外,帧中还可能出现鼠标、键盘及其他事件,这些事件也需要被处理和完成。最终,Flash Player 将完成它对于这个帧的所有任务,不留下任何未完成事情。然后它坐下来等待下一次心跳,以便再次开始一个新的帧!
在 Scout 中,需要关注的主要是越过红线的彩色竖条。这表示 Flash Player 完成它需要完成的所有事情花费的时间长于整个帧的预算时间。换句话说,Flash Player 在分配给它的时间内有太多的事情要做!如果所有的帧都超出预算时间,那么您的内容通常就会变慢(见图 1),而如果有很多参差不齐的尖峰,内容就会不流畅。
注意,即使您早早地完成帧,Flash Player 在开始下一帧之前也总是会等待。这是一件好事情——表示您的内容不会一直霸占着设备上的所有资源,这可以降低电力消耗,腾出 CPU 时间来执行其他任务。这也表示您的内容中内置有时差,以便可以按目标帧速率运行在较慢的设备上。灰色竖条徘佪在红线周围的情况极为正常(见图 2)。
关于帧,需要了解一些琐碎的事情:
- Flash Player 的最大帧速率是 60fps。设置更高的帧速率也不会让内容以比这更快的速度运行。记住,大多数显示器不支持超过 60fps,所以试图将屏幕帧速率更新得比这快完全是徒劳无功的!
- Flash Player 尽量与操作系统配合良好。您浏览 Web 时,可以有很多的播放器实例在运行,并且帧速率可以各不相同。Flash Player 不是在随机时间唤醒播放器实例并降慢计算机速度,而是试图同步不同播放器实例的唤醒时间。大致来讲,它每 1/60 秒唤醒一次,并开始任何已就绪播放器实例中的一个新的帧。这表示,如果将帧速率设置为不是刚好平分为 60,比如说设置为 24fps,那么您将看到灰色竖条徘徊在红线周围——时上时下(见图 3)。这是正常的,只要平均上符合目标帧速率就不是问题(您可以在 Scout 中选择某个范围的帧,来查看平均帧速率)。
- 如果在 Scout 中看到灰色竖条一直越过了红线,即使彩色竖条(活跃时间)总是低于红线,那么计算机上可能运行着其他应用程序,在使用 CPU 资源。请关闭其他应用程序和浏览器窗口。
Flash Player 的组件
既然已经较好地了解了 Flash Player 的基本结构,现在就该进一步来了解 Scout 中看到的数据。您会注意到,Top Activities 和 Activity Sequence 面板给出了 Flash Player 正在做的事情的详细分类。这些面板中的描述尽可能地一目了然,但是有时候您也需要更多的信息来了解问题的根源。本节将 Flash Player 的各种活动划分为几个功能区域(见图 4),并对它们的含义提供了更详细的解释。
您可以将本节作为使用 Scout 时的参考指南,只阅读跟具体问题有关的部分。或者,您也可以阅读整个这一节,更多地了解 Flash Player 的各个部分是如何工作的。
ActionScript 3 和事件处理
ActionScript 是 Flash 语言;它让您告诉 Flash Player 去做什么。您建立一个 SWF 时,ActionScript 代码会被编译成一种 Flash Player 可以理解的较低级的语言,即 ActionScript 字节码。当 SWF 运行时,该字节码会由 ActionScript 虚拟机 (AVM) 执行。AVM 实现 ActionScript 的某些核心功能,包括垃圾收集和异常,还充当您的代码与 Flash Player 之间的桥梁。
您可以使用 API 从 ActionScript 与 Flash Player 交互。对于您来说,这些 API 就像普通的函数调用,但是在底层,您经常是在调用原生 C 代码——这里就是奇迹发生的地方!在 Scout 中, 这些 API 在 ActionScript 面板中显示为普通的函数调用,您无需担心内部细节。如果在一个 API 调用中花费了太长的时间,您就应该少调用它或者给它分配较少的工作去做(通过缩小参数的大小或复杂度)。
从 ActionScript 调用 Flash Player 时,您需要告诉 Flash Player 何时执行您的代码。大多数时候,这使用事件处理程序来完成。这些事件处理程序是您注册的一些函数,旨在特定事件发生时进行调用。您可以创建定制事件,并使用 EventDispatcher.dispatch()
手动调用它们,但是您会经常需要侦听外部事件,比如说鼠标移动和键盘按键。Scout 中有很多与此相关的活动:
- Handling event "<name>" – Flash Player 总是在侦听和接收来自操作系统的外部事件。接收到一个事件时,它首先必须进行一些处理,然后得出应该调用哪些事件处理程序(例如,对于键盘事件,调用的处理程序取决于哪个对象拥有键盘焦点)。下面是您会在 Scout 中看到的一些外部事件:
- 键盘事件:
keyDown
,keyPress
,keyUp
. - 鼠标事件:
mouseDown
,rightMouseDown
,mouseMove
,mouseUp
,mouseWheel
. - 触摸和手势事件:
touch
,gesture
. - 窗口事件:
resize
,mouseLeave
,foreground
,background
,fullScreen
,fullScreenInteractiveAccepted
,visible
.
- 键盘事件:
- Event "<name>" – 这是事件 <name> 的实际 ActionScript 事件处理程序。这个事件处理程序只有创建并注册之后您才会看到(这适用于任何事件,不只是针对上面提到的这些事件)。下面是一个小提示:在 Scout 的 Activity Sequence 或 Top Activities 面板中单击一个事件,可以过滤 ActionScript 面板,使之只显示正在这个特定事件处理程序中执行的代码。
定时器的处理方式非常类似于外部事件,只不过它们是由您自己设置的:
- Handling event "timer" – 向任何已注册的事件处理程序分派一个
TimerEvent.TIMER
事件。 - Timer started:<interval> – 表明定时器已经启动,定时器事件之间的延迟是 <interval> ms。
其中也会有很多与执行您的代码及支持 ActionScript 语言特性相关的 AVM 活动:
- Garbage collection – 与 C 以及其他需要您来进行内存管理的语言不同,ActionScript 会为您进行内存的收集和释放。Flash Player 会定期地运行垃圾收集器,以扫描任何不再被引用的对象(没有谁引用它们了),并释放它们在使用的内存。如果您的内容花费大量的时间进行垃圾收集,那么表明您创建了太多的对象。您可能试图想要高速缓存或池化对象,避免总是创建新的实例。
- Trace:<output> – 这给出 ActionScript 代码中任何
trace()
语句的输出。也可以在 Trace Log 面板中查看该数据。 - Exception:<class> – 这给出对 ActionScript 代码执行期间出现的任何异常的堆栈跟踪。即使异常被捕获并处理了,这也会显示出来,所以在 Scout 中看到这样的信息并不一定代表您的代码有问题。
- Handling uncaught error – 向任何已注册的处理程序分派一个
UncaughtErrorEvent.UNCAUGHT_ERROR
。
最后,如果您从其他语言调用了 ActionScript 3 代码,还会看到一些活动:
- AVM Bridge callback – 从 ActionScript 2 对 ActionScript 3 代码的调用。
- ExternalInterface callback – 从 JavaScript 代码对 ActionScript 3 代码的调用。
ActionScript 工人
如果您在内容中使用了 ActionScript 工人,那么它们将作为单独的会话出现在 Scout 中。在底层,它们实际上就是单独的播放器实例,带有一个用于在它们之间共享数据的 API。在 Scout 中,对工人的支持目前还是一个 beta 特性。为启用支持,您需要做以下事情:
- 在 Scout 中,单击 Preferences > Beta Features,并选择 Start Sessions For ActionScript Workers。
- 在 Scout 的侧栏中(如果不可见,请单击 Window > Sidebar),确保 ActionScript Sampler 已启用。
- 确保您的内容已经选择为支持遥测技术(如果不知怎么做,请参见入门指南)。
对于使用 ActionScript 工人的内容,您可能会在 Scout 中看到以下活动:
- Running Worker code – 如果启动一个工人,并继续运行其中的一些 ActionScript 代码(工人执行单个长时间运行的函数,而不是由事件驱动的),您将在 Scout 中看到此活动。这表示您仍然处于工人的顶层入口点,所以它不对应于任何事件。
- Waiting for Condition – 在对
Condition.wait()
的调用上阻塞。 - MessageChannel.receive – 在对
MessageChannel.receive()
的调用上阻塞。 - MessageChannel.send – 在对
MessageChannel.send()
的调用上阻塞。 - Mutex.lock – 在对
Mutex.lock()
的调用上阻塞。 - Mutex.trylock – 在对
Mutex.trylock()
的调用上阻塞。
用户活动
除了 Flash Player 所做的内置测量,您还可以使用 Telemetry API 向 Scout 发送定制数据。两个主要的调用如下:
Telemetry.sendMetric(name, value)
– 这向 Scout 报告一个名-值对,将显示在 Activity Sequence 面板中。例如,如果调用Telemetry.sendMetric("UserID", 2)
,您将在代码执行时看到 "UserID:2" 出现在 Activity Sequence 面板中。Telemetry.sendSpanMetric(name, startTime, value)
– 这向 Scout 报告一个活动,带有一个可选值。您通过查看Telemetry.spanMarker
记录开始时间,然后在您想要测量的周期末尾将之传递到Telemetry.sendSpanMetric
。它将同时显示在 Scout 的 Top Activities 和 Activity Sequence 面板中。
有关更多信息,以及如何使用该 API 的例子,请参见 关于 Telemetry API 的文档。
帧滴答器
Flash Player 的核心是帧滴答器–每当一个新帧开始时发生的心跳。在每个帧的开始,它都会执行任何时间轴标记,调用时间轴上的任何帧脚本,并分派一些关键的 ActionScript 3 事件。帧滴答器的活动如下:
- Running SWF tags for frame – Flash Player 执行与该帧相关的 SWF 标记。这通常对应于您在 Flash Professional 所做的事情;例如,创建附加到帧的对象,并根据补间移动这些对象。
- Running frame actions – 这些是您在 Flash Professional 中使用 Actions 面板附加到时间轴上某个帧中的脚本。该脚本可以是 ActionScript 2 或 ActionScript 3。对于 ActionScript 3,可能不会被立即执行,而是被放置在帧结束之前 Flash Player 会执行的一个队列中。
- Running AS3 attached to frame – 执行任何在 Flash Player 运行附加到时间轴上的某个帧上的脚本时放在队列中的 ActionScript 3 代码。
- AS2 event "enterFrame" – 用 ActionScript 2 编写的
MovieClip.onEnterFrame
事件处理程序。 - Handling event "enterFrame" – 当一个新帧开始时,Flash Player 寻找并调用
Event.ENTER_FRAME
的任何已注册处理程序。 - Handling event "frameLabel" – 当一个新帧进入后,如果该帧具有一个 FrameLabel 对象,且该对象为
Event.FRAME_LABEL
带有已注册处理程序,那么该处理程序就会被调用。 - Handling event "exitFrame" – 在当前帧临结束之前,Flash Player 寻找并调用
Event.EXIT_FRAME
的任何已注册处理程序。
显示列表呈现
显示列表是 Flash Player 中经典的呈现方法。简单来说,给您一个称作画布的空白屏幕,您通过附加和布置称作显示对象的图形实体,在画布上绘画。有几种不同类型的显示对象,包括矢量插画、位图和文本,它们可以逐层嵌套来构成复杂的场景。无论是从 ActionScript 与显示对象交互,还是通过在 Flash Pro 中布置它们和设置补间,您都无需担心它们是如何真正呈现的。Flash Player 为您完成这项艰难的工作,计算出如何将显示列表转换成您在屏幕上看到的实际像素。
如果是使用 ActionScript 来操纵显示列表,您会注意到有好几种 API 您可以调用来进行更改。您可以将这些更改看作为即时活动。您调用 API 时,它会立即修改显示列表的内部状态。它不做的是立即修改您在屏幕上看到的内容!相反,Flash Player 将您修改的任何显示对象标记为脏的,这表示它们需要重绘。稍后某个时候,在您的代码执行完成之后,Flash Player 将执行一个呈现通道。它将所有的更改收集到一起,因此只需更新一次屏幕,大大提高了效率。您可以将这想象成单格拍制动画。在一个帧当中,您到处捏制好所有的小泥人,完成之后,Flash Player 再来拍摄这一帧的场景图。
这就是呈现通道在 Scout 中的样子(见图 5):
- 计算脏区域 – 呈现循环开始于扫描显示列表,寻找任何被标记为脏的对象(例如,移动了的对象)。然后它最多生成三个矩阵,其中包含所有的脏对象。 这些是需要重绘的屏幕区域。您可以在 Scout 的 DisplayList Rendering 面板中看到这些区域,即红色区域。
- 呈现脏区域 – 对于每个脏区域,Flash Player 必须计算出新像素应该是什么样的。它通过在一个称作显示缓冲区的内部缓冲区中构造场景来做到这一点。这个过程涉及到一些步骤:
- 从 DisplayObject 构建边缘– Flash Player 再次扫描显示列表,寻找与脏区域重叠的显示对象。对于找到的每个这样的对象,它会将之分解成一组边缘和填充(填充可以是纯色、渐变或者定义边缘之间的空间如何上色的位图)。如果显示对象包含其他显示对象,就会对它们递归地应用这同一过程。本质上,Flash Player 将分层的场景平面化为不同的填充区域。
- 栅格化边缘 – Flash Player 现在必须将边缘和颜色转换成实际的像素。它将脏区域分解成称作扫描线的水平线,每个垂直像素一条。扫描线在机器内核之间共享。Flash Player 沿着每条扫描线,根据这条线周围的边缘之间的区域的填充,计算出覆盖每个像素的颜色是什么。这个过程花费的时间取决于像素数量以及绘制每个像素的成本。绘制纯色的代价最低,而绘制带有多层 alpha 混合的旋转和缩放位图则相当昂贵!
- 应用过滤器:<name> – 如果您设置了任何过滤器,比如光晕或投影效果,那么这些过滤器现在将被应用于已栅格化的图像。但是对于子显示对象的过滤器,则必须在父对象被栅格化之前加以应用。Flash Player 通过将子显示对象栅格化到一个单独的表面(在 Scout 的 DisplayList Rendering 面板中显示为蓝色),并将过滤器应用于该表面,来做到这一点。这将导致一个新位图,该位图在为父显示对象构建边缘时被当作一个原子填充。
- 复制到屏幕 – 在呈现通道的末尾,Flash Player 从它的显示缓冲区将数据复制到屏幕。这个过程也叫做展示。该过程花费的时间主要取决于必须复制的像素数量,但是在从操作系统获得锁的过程中也可能存在延迟。如果您使用了硬件加速,就意味着显示缓冲区已经在显卡上了,Flash Player 只需告诉显卡显示它即可。这应该显示为 Waiting for GPU 时间(参见关于 GPU 相关性能问题的一些注意事项,了解更多信息)。如果不是这样的,请检查您是否使用的是最新版 Flash Player(11.5 或更晚版本)。
如果绘制带有很多边缘和不同过滤器的复杂形状,那么呈现将会很昂贵。请将显示列表修改为尽可能地小,以减小每个帧中需要重绘的脏区域的数量和大小。如果是绘制一个很少修改(只四处移动)的复杂的嵌套对象,那么您可以通过高速缓存它来改善呈现性能。这会将显示对象栅格化到一个高速缓存表面,该表面发生更改的话只需重新生成即可。如果只平移显示对象,那么请设置 cacheAsBitmap
属性。如果还想要对它进行旋转和缩放,那么请使用 cacheAsBitmapMatrix
(只在手机上受 AIR 支持)。
Scout 显示以下与高速缓存表面(在 DisplayList Rendering 面板中显示为橙色)相关的活动:
- Rendering from cached surface – 如果显示对象具有有效的高速缓存,那么 Flash Player 将使用该活动,而不是构建并栅格化它的边缘。
- Updating cached surface – 如果您修改已高速缓存的显示对象或者它的子对象,那么将使高速缓存失效,需要重新创建它。如果 Flash Player 花费太多的时间更新高速缓存表面,就表明您使用高速缓存不当。高速缓存应该只使用于很少更改的显示对象,否则不进行缓存更快一些!
作为每个呈现通道的一部分,Scout 将显示关于在某些特定类型的显示对象上执行的活动的附加信息。以下活动与呈现文本相关:
- Updating text layout – 一个文本显示对象发生了更改(例如,更改了 TextElement 或 TextField 对象的文本属性),需要再次布局。这也会将显示对象标记为脏的,以便在下一个呈现通道中得到重绘。
- Rendering text – 使用 TextField 类呈现文本。
- Rendering FTE text – 使用 flash.text.engine 包中的 Flash Text Engine 类呈现文本。
以下活动与 Bitmap 对象及操纵它们的相关位图数据 (Bitmap.bitmapData
) 相关:
- Creating BitmapData – BitmapData 对象的构造函数。这只出现在 Scout 的 DisplayList Rendering 面板中。
- Decompressing images – 您加载压缩图像时,比如 JPEG 或 PNG,需要先进行解压,然后 Flash Player 才能显示它。如果觉得这个过程花费的时间太长,您可以告诉 Flash Player 使用 异步解码,异步解码是使用一个后台线程在加载的过程中解压图像,所以不会阻塞您的 UI。
- BitmapData.copyPixels – 对
BitmapData.copyPixels()
的调用。 - BitmapData.draw – 对
BitmapData.draw()
的调用。
您可能会在 Scout 中遇到几个与显示列表呈现相关的额外操作:
- Creating Display Buffer – 该过程创建显示缓冲区,Flash Player 将显示列表栅格化到该缓冲区。这通常应该在播放器实例创建时发生一次。
- Resizing Display Buffer – 该过程调整显示缓冲区的大小,一般在画布重新调整大小时进行调整。
- Triggering UpdateAfterEvent – 一个呈现通道将要开始,因为您调用了
updateAfterEvent()
。一般来说,如果设置了较高的帧速率(比如 30fps 或 60fps),那么您应该永远不需要调用该函数,因为 Flash Player 已经每个帧至少执行了一个呈现通道。 - 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 面板中还可以找出哪些调用是最慢的。
- 在 GPU 上 – 最终还是显卡完成绝大部分工作,比如实际的呈现。Scout 目前不提供任何关于 GPU 处理时间的信息,但是您可以在 Summary 面板中查看 GPU 内存使用情况。如果您认为瓶颈在 GPU 上,请参见下面这一节关于 GPU 相关性能问题的内容。
SWF 加载器
开始一个新的播放器实例时,Flash Player 首先必须下载、解析主 SWF,并将之加载到内存中,然后才能开始执行它。您将在 Scout 中看到与之相关的以下活动(注意,与 Flash Player 的网络组件有一定程度的重叠):
- Loading SWF:<url> – Flash Player 将要加载的主 SWF 的 URL。运行独立版本的 Flash Player 时会看到这个 URL。
- Loading file:<url> – 在给定 URL 下载文件。
- Receiving Loader data – 通过网络接收您在使用
Loader.load()
加载的资源的数据。每次接收到数据时,一个ProgressEvent.PROGRESS
事件都会被分派给任何已注册处理程序。 - Receiving SWF data – 接收一些作为 SWF 文件一部分的数据。
- Receiving image data – 接收一些作为图像(比如 JPEG、PNG 或 GIF)一部分的数据。
- Decoding SWF file – 解析 SWF 数据,并根据需要解压缩。
- Closing Loader – 关闭加载器使用的网络连接。导致该活动的原因可能是加载完成、发生错误或者调用
Loader.close()
。 - Preparing ActionScript bytecode – 解码 ActionScript 字节码,使之成为可执行的形式。
- Initializing AS globals – 初始化在任何函数或类定义之外定义的变量。
- Handling event "onLoadInit" – 用 ActionScript 2 编写的
MovieClipLoader.onLoadInit
事件处理程序。
网络
Flash Player 支持的网络操作有三种主要类型:本地连接、TCP/HTTP 连接和流媒体。无论使用哪种类型,开始一般是设置网络连接或发出请求。网络操作比较费时,所以不是同步发生的。一旦您发出了请求,ActionScript 代码就可以在 Flash Player 在后台处理网络操作的同时执行其他任务。通过为相关事件注册事件处理程序,可以了解此操作何时完成或者状态发生更新。
要让同一台机器上的两个播放器实例进行通信(例如,假设加载了一个帮助器 SWF),可以设置一个 LocalConnection 对象,来让一个实例调用另一个实例的函数。Scout 显示以下与本地连接相关的活动:
- LocalConnection callback – 由于在发送 LocalConnection 对象时调用了
send(connectionName, functionName, arguments)
,所以在接收 LocalConnection 对象时要调用functionName(arguments)
。 - Handling LocalConnection traffic – 执行本地连接的一般维护,包括发送和接收 LocalConnection 对象的任何数据。
想要下载内容时,比如图像和其他 SWF,您通常使用 Loader 或 URLLoader 对象,它们使用 HTTP。您也可以使用 HTTP NetConnection 从服务器发送或接收数据。Scout 显示以下与下载相关的活动:
- Sending URL requests –利用诸如
NetConnection.call()
之类函数通过 HTTP 发出的请求由 Flash Player 进行排队。这些排好队的请求定期地通过网络发送出去。 - URL request timestamp – 发送 URL 请求的时间点。
- URL request:<url> – Flash Player 正在请求的 URL。
- URL request ID:<id> – URL 请求的惟一识别符。
- Responder callback – 由于
NetConnection.call()
成功或返回错误,所以调用一个您传递给 Responder 对象构造函数的函数。 - Processing network buffers – 一般网络开销,比如说遍历缓冲区以查看是否有任何数据达到。
Flash Player 也为来自服务器的流媒体(比如音频和视频数据)支持很多协议。可以使用 NetConnection 创建 NetStream,并指定协议。实时消息传递协议 (RTMP) 运行在 TCP 之上,实时媒体流协议 (RTMFP) 运行在 UDP 之上。在 Scout 中可以看到以下与流式传输相关的活动:
- Receiving NetStream audio/video – 从服务器接收大量音频或视频数据。
- Decoding media from network – 解压缩通过网络接收到的音频或视频数据,以便可由 Flash Player 播放。
- Receiving NetStream metadata – 接收关于所请求媒体的元数据,比如创建时间、持续时长、主题等。通过设置
NetStream.client
对象的onMetaData
属性,可以注册一个在接收到元数据时调用的事件处理程序。 - Receiving NetStream commands – 处理来自服务器的命令消息。例如,这些命令通知 Flash Player 关于其以前请求的命令的状态。
- Closing network connection – 导致该活动的原因可能是流传输结束、发生错误或者调用
NetStream.close()
。 - Receiving NetStream shared objects – 接收存储在服务器上(以便可以跨多个客户端共享)的 ActionScript 3 对象的数据。这可能是由于调用了
SharedObject.connect()
。
声音
在多数台式机上,Flash Player 播放声音的效率很高。当对诸如 Sound 或 SoundChannel 之类的对象调用 flash.media 包中的函数时,执行声音操作花费的时间将显示在 Scout 的 ActionScript 面板中。您也可能看到下面这个活动:
- Dispatching Sound Complete – 用 ActionScript 2 编写的
Sound.onSoundComplete
处理程序,或者是用 ActionScript 3 编写的针对 SoundChannel 对象的Event.SOUND_COMPLETE
处理程序。
视频
Flash Player 中的视频播放基本上是一个长期运行的网络操作。当您将想要播放的视频告诉 Flash Player 时,Flash Player 就会在后台发起一个持续活动。通过网络定期到达的数据被解压,然后显示在屏幕上。取决于平台、编解码器以及其他视频设置,需要的 CPU 时间差异很大。
Scout 目前不提供关于视频性能的很多信息,但是您可能会看到下面这个活动:
- Initializing StageVideo – 设置 StageVideo 对象。
其他活动
还有一些无法归为以上类别的其他 Flash Player 活动:
- Running AS2 – 执行 ActionScript 2 代码。即使您没有编写任何 ActionScript 2 代码,也可能显示该活动,因为 Flash Player 会自动生成一些 ActionScript 2 代码来执行某些初始化和内务处理功能。
- AIR startup – 在启动时为 AIR 应用程序运行 ActionScript 代码。
- Button hit testing – Flash Player 具有一些特殊的代码来处理老式的按钮对象(在 Flash Professional 中创建的对象)。除了为鼠标事件寻找 ActionScript 事件处理程序之外,每当鼠标移动时 Flash Player还会为这些按钮搜索显示列表。如果显示列表中有大量的对象,那么这个搜索过程将很昂贵。不幸的是,即使没有使用老式按钮对象,也会发生这一操作,不过 Adobe 正在着手解决这个问题。
- Runtime overhead – 这包括任何其他活动没有计算在内的时间。Flash Player 只测量通常花费大量时间的活动,所以它做的所有其他小事情都归为这一类中。这类活动使用的时间不多,可以忽略不计,您无需关注它。
不活跃时间
Flash Player 在等待发生某事情时,Scout 中显示为不活跃时间。这可能是因为在某件事情上阻塞了,或者只是已经完成了所有的工作。展开 Scout 的 Summary 面板中的 Inactive 类别,会看到这个时间分为以下部分或所有种类:
- Waiting for next frame – 当前帧中没有事情可做了,所以播放器实例进入睡眠,直到开始下一帧;或者发生了一些外部事件(比如鼠标或网络事件)。
- Waiting for GPU – Flash Player 在等待显卡执行某些操作。参见关于 GPU 相关性能问题的一节内容,了解详细信息。
- Waiting for condition – 工人线程阻塞在
Condition.wait()
、MessageChannel.send()
或MessageChannel.receive()
调用上。
Scout 中的内存报告
就跟测量执行各种活动花费了多长时间一样,Flash Player 也跟踪它在使用多少内存。在 Scout 的 Summary 面板中可以看到该报告,就像 CPU 时间一样,内存使用情况也分为几类。Flash Player 只跟踪它显式分配的内存——有一些内存是操作系统分配的,比如说用于存储 Flash Player 可执行文件的内存,这不会显示在 Scout 中。
在 Scout 中查看一个会话的内存使用情况时,一定要明白总内存是所有正在运行的播放器实例使用的内存。其他播放器实例使用的内存出现在 Other 类别中,位于 Other Players 下面。此外,Uncategorized 类别下的一些内存可能是其他播放器实例使用的。一些数据结构在 Flash Player 中是共享的,因而难以归属于特定的播放器实例。在浏览器中剖析内容时,可能的话最好只运行 SWF。
Flash Player 尽力跟踪使用大量内存的主要数据结构,但是某些区域比另外一些区域涵盖得更好一些。目前,它测量音频和视频缓冲区或者 JITted ActionScript 代码使用的内存。这将和任何其他未跟踪的内部数据结构一起显示在 Uncategorized 类别下。
关于 GPU 相关性能问题的一些注意事项
GPU 是显卡上的专用处理器,用于硬件加速呈现。它擅长于大规模并行计算,比如说定位和着色巨大的三角形阵列。这是与 CPU 进行的比较,CPU 可以完成更复杂的任务,但是只擅长于一次做一件事情。Stage3D 通过提供一个 ActionScript API 来利用 GPU,该 API 的作用是让您可以直接与之沟通。为了工作更为轻松,通常会用到一个较高级别的框架,比如针对 2D 内容的 Starling 和针对 3D 内容的 Away3D。但是请记住,这些框架是在幕后使用 GPU。
使用 Stage3D 时要记住的一件事情是,GPU 与 CPU 并行运行。这意味着,内容的速度局限于那个最慢的组件。即使您的 ActionScript 代码很快(所以 CPU 没有超载),如果分派 GPU 太多的工作,那么也不会得到您想要的帧速率。下面是使用 GPU 时的两个最常见性能问题:
- 您可能在要求 GPU 去做太多的工作。最常见的原因是发出太多的绘制调用,更改 GPU 状态太频,或者使用太大的网格和纹理。有大量优化内容的方法。例如,使用压缩纹理,在 Starling 中使用纹理贴图集, 当然也包括简化试图呈现的对象。目前,Scout 还显示不了 GPU 正在做什么,但是您可以使用 Scout 的 Stage3D Rendering 面板来查看正在执行什么 Stage3D 命令,并寻找优化的方法。
- 您可能在尝试比 GPU 可以达到的速度更快地更新屏幕。显卡以一种称作双缓冲的技术工作。一个缓冲区包含当前显示在屏幕上的数据,而另一个缓冲区(后台缓冲区) 用于构造下一个要显示的图像。调用
Context3D.present()
时是在告诉 GPU 显示您所绘制的内容,意味着 GPU 应该轮换这两个分区。但是 GPU 每 1/60 秒才对它们轮换一次。这就是所谓的 VSync,它跟显示器更新图像的速度相关。如果 GPU 不能轮换,它将阻塞,这在 Scout 中显示为 Waiting for GPU。这通常不是问题,但是如果 CPU 跟不上 VSync 速率,它可能再三地错过最后期限,然后必须等待下一个 VSync,GPU 才能更新。如果发生此情况,通常可以通过设置较低的帧速率来避免额外的等待时间,从而改善性能。
下面是一个重要的提示:如果您的内容在 Scout 中显示过多的 Waiting for GPU 时间,请尝试在 Flash Player 中临时禁用硬件加速(右击并选择 Settings)。这将导致 Flash Player 退回到软件呈现,等待时间将会消失,所以您可以确认问题是 GPU 相关的。
下一步阅读方向
既然更多地了解了 Flash Player 的工作方式,那么接下来最好能够使用 Adobe Scout 来优化内容。记住,如果在使用 Scout 时忘记了一些指标的含义,您可以使用本文作为参考。祝您学习快乐!
Adobe Scout 使用参考说明