从 Flash Player 执行模型看 chrome41中V8 运行原理
这是一篇很老的文章,最近在聊一些 V8 的机制时,想到了一些,发现有着非常多的相似。
Flash Player 原文地址:http://blogs.adobe.com/xwlin/2010/04/flash_player_101_-_adobe_max_2009_1.html
理解执行模型
执行模型是指Flash Player在每一个帧周期中如何执行相应的指令操作。Flash Player后台事实上运行着n多线程,只是AS并没有给开发人员提供多线程编程模型。这意味着从概念上来讲我们要把Flash Player看做是单线程运行实体,有关这一单线程编程模型的优势/劣势的争论从未休止过,我不想对这一具有争议性的问题做过多评论,但请大家记住这一事实。
可变跑道(Elastic Racetrack)
可变跑道是Flash Player的帧执行模型,这个模型描述了在一帧的处理周期中,代码执行和帧渲染的工作是怎样彼此平衡的。Flash Player 9和AVM2对这一模型进行了一些改进,这一信息是基于对事件机制和渲染模型的研究总结出来的,完整的模型尚未被官方公布。
基本的跑道理论没有发生改变,在Flash Player执行一帧的周期里,前一部分时间用于执行代码,剩余时间用于渲染显示列表中的对象。每个执行阶段都可以根据实际需求增加执行时间来执行更多代码或做更多的渲染工作,而跑道的总长度也将相应增长。
在前一模型基础上发生改变的是每一阶段在一个微观周期里的样子以及他们怎样形成一帧。
AVM2是由Flash Player中一个叫做Marshal的元帅级组件所操控,Marshal负责将时间切割成Flash Player工作所依的基本时间片,在这里我希望澄清一下Flash Player的时间片跟swf文件运行时的帧速率没有任何关系,我们将最终看到Flash Player是如何将这些时间片合成为一帧。在Mac OS版Firefox中执行一个由Flex编译得来的swf文件,Marshal通常会将时间切割成19-20毫秒的时间片,时间片大小根据平台和浏览器的不同而存在差异.为方便我们接下来的讨论,我们假定时间片大小为20毫秒,也就是说Marshal每秒钟会产生不超过50个时间片,每个时间片中,五步可能的操作按如下顺序执行:
Player事件调度 - 比如Timer事件,鼠标事件,ENTER_FRAME事件,URLLoader事件等等。
用户代码执行 - 所有侦听上一步相应事件的代码被执行。
RENDER事件调度 - 在用户代码执行期间调用stage.invalidate()会触发这一特殊事件。
最后的用户代码执行 - 侦听上述第三步特殊事件的用户代码此时被执行。
Player更改显示列表。
Marshal如此反复的执行20毫秒时间片并在运行中决定下一步操作。一个时间片中执行的所有这些操作最终归纳为上述两段式跑道(代码执行,图像渲染)也就是我们所说的一帧。用户代码和失效操作填充在代码执行区,渲染操作填充在跑道的渲染区段。需要指出的是相关操作只能在Marshal预定的时间内发生,如果你的用户代码很短,那么Marshal仍然会在执行完用户代码后等待一段时间然后进入渲染阶段。
为了更好阐述哪些action被如何执行以及可变跑道如何被创建,请参考如下示例,分别描述了以5fps, 25fps和50fps帧速率工作的swf中时间片是如何被处理的。
以上示例可以看出,不同的帧速率下,一个帧周期中的可变跑道会执行不同操作,例如对于5fps的swf,每帧处理10个用户action,1个失效action,1个渲染action;帧速率25fps的swf,每帧处理2个用户action,1个失效action,1个渲染action;对于50fps的swf,每帧只能处理1个用户action,1个失效action,1个渲染action。需要指出的很重要的一点是,某些事件只可能能发生在某些特定的时间片里,比如,Event.ENTER_FRAME事件只能在某一帧的初始时间片中被调度。
一个时间片中代码部分和渲染部分都有可能过长而导致相应时间片大于20毫秒,这就是"可变"的含义,为了保证帧的播放速率仍然接近swf编译时设定的帧率,Marshal会选择丢掉一些时间片。
swf文件的播放速率不可能超过当前Flash Player切割时间片的速率,你可以为swf文件设定120fps的播放速率,但Flash最多可以按照50帧的速度播放(具体数值取决于当前系统的设置)。
代码的执行频率可能比swf的帧率更高,播放一个1fps的swf文件,播放一帧时间为1秒,也就是50个时间片,但在每个时间片里,都会有触发鼠标或计时器事件,尽管只有最后一个时间片才会渲染,另外你可以选择通过调用函数updateAfterEvent() 提前渲染,但只能在鼠标,计时器和键盘事件处理函数中调用。在这种情况下,Marshal会认为当前帧已结束并从下一个时间片起进入下一帧。 最后,如果一个Sprite的外观属性比如width,height等发生变化而将鼠标从该Sprite上方掠过,Flash会进行强制渲染。
如果帧率不是每秒时间片数量的整数因子,那么该平台的渲染时间间隔将变得不固定,比如帧率20fps的swf运行在50个时间片每秒的系统上,Flash Player的行为将是每5个时间片播放两帧,那swf的渲染频率将是2-3-2-3-2-3(时间片)。
【博主】
在9RIA上,有关于该文章的进一步讨论,主要是updateAfterEvent()到底能不能结束当前帧而进入下一帧,下面是讨论内容:
【jinni】
我一直对这篇文章的结论有质疑,
根据我测试的结果,当调用updateAfterEvent()时,FP并不认为是一帧的结束,而只是单纯的重绘屏幕
例如你可以在一帧中点击20次鼠标+updateAfterEvent(),并造成20次重绘事件(RENDER),但对于Flash Player的帧事件(ENTER_FRAME),只会触发一次。
换句话说,帧的运行次数和重绘屏幕的次数是没有任何关系的
【jinni】
其实有一个很简单的论证
很多Flash的逻辑依赖于ENTER_FRAME执行
如果调用updateAfterEvent()就会立即结束当前帧从并触发下一帧的话
那么这些Flash的运行逻辑就会加快了,这显然不合理
而事实上(根据我的测试),调用updateAfterEvent()只会触发RENDER事件和系统重绘
另外,Flex中的Mouse事件,默认会调用updateAfterEvent()来保证运行效果
【Aone】
我测试下来不会打断。不过确实有种可以打断帧的方法,就是给 stage.frameRate赋值,即便是stage.frameRate = stage.frameRate也会打断帧强制该帧结束。
测试代码如下:
stage.frameRate = 1;
var timer:Timer = new Timer(1);
timer.addEventListener(TimerEvent.TIMER,onTimer);
timer.start();
addEventListener(Event.ENTER_FRAME,test);
var old:int = getTimer();
function test(e:Event):void {
trace(getTimer()-old);
old = getTimer();
}
function onTimer(e:TimerEvent):void {
//stage.frameRate = 1;
e.updateAfterEvent();
trace("timer");
}
【jinni】
我所做的测试都是Mac和Win同时做的
事实证明updateAfterEvent()是不会造成帧的结束
而且,在实际情况中,updateAfterEvent()并不只是用户手动调用才会触发
Flash Player会根据需要来对自身进行强制重绘,一帧中可能触发数十次RENDER事件和重绘
而原文所描述的模型基于一个假设:“一帧之中用户事件代码会执行多次,但渲染事件代码和渲染过程只会执行一次。”就是不成立的。
【Aone】
updateAfterEvent()必须Timer触发或者鼠标事件还有键盘事件触发后才能正确重绘。
不过呢... 鼠标和键盘就算不注册侦听在某些情况下也会自动updateAfterEvent()来强制渲染。
【jinni】
我和Ted讨论了一下,这是他的回复:
Yes you can call updateAfterEvent over and over between standard renders. You basically inject a partial render in the code execution side of the racetrack and elongate it.
I would love to see AS have full render control as a playermode in FP11. If set there are no automatic renders and the developer must call render for all visual changes to occur.
是的,你可以在每次标准RENDER(即原文中的Last slice's render)之间调用多次updateAfterEvent(并进行重绘),相当于你在跑道一圈中代码执行的阶段"注入"了部分渲染过程并延长了整个跑道。
其实我很希望看到Flash Player 11可以在某种模式下给开发者完整的渲染控制。在这种情况下开发者必须主动调用API来实现屏幕重绘。
我想问题其实已经比较清楚了,文中跑道模型描述的只是其中一种形态,很多时候,渲染和用户代码的调用在一帧的过程中,是穿插进行的,而不是简单的先执行用户代码,最后渲染。
下图是 V8 的时间片执行过程
上图中我们看到,只一个时间片段内,与Flash Player有着惊人的相识。 Flash Play 在这其中有一个落后于 V8 的设设计。
在一个时间片内,Flash 只是设计了执行 代码逻辑与重回。 而 V8 进行了扩展,在单一时间片内,执行了小范围的GC处理。这样设计的好处是避免了内存垃圾过多,积攒过多,而调用一次GC处理这些,非常浪费时间。这样就是游戏中经常可能出现的偶尔掉帧。