关闭

webkit之渲染主循环理解

248人阅读 评论(0) 收藏 举报
分类:

# Chromium渲染主循环(mainloop)和requestAnimationFrame

## 概述

曾经写过一段JavaScript代码,因为涉及到需要循环调用某个函数来实现动画的功能,很自然地,我想到了使用setInterval函数(或者setTimeout,大家是否有类似经历呢?),然后心满意足地很快的搞定。结束后,朋友帮忙阅读了一下代码,他提醒我是不是可以考虑使用requestAnimationFrame。之前一直知道这个函数,也知道一些它的一些优点,问题是为什么呢?本着追究到底的精神,决定还是去阅读一下WebKit相关代码和一些相关文档,了解它们背后的故事。好吧,本章我将和大家一起来学习和探讨这背后的故事…

 

## 背景

接触过JavaScript的读者应该有过了解或者使用setTimeout或者setInterval的经历,其功能是在每个时间间隔之后一次性或者重复多次执行一段JavaScript代码(称为回调函数),以完成特定的动画要求。但是,这里面有还有些疑问:

1.      时间间隔应该设置为多少才合适呢?跟屏幕的分辨率有关系吗?

2.      设置的时间间隔会按照预想的执行吗?动画会被平滑地显示出效果吗?

3.      回调函数是复杂的好还是简单的好呢?应该如何编写才能效率高呢?

4.      与平台和浏览器相关吗?如何适应不同平台呢?

这对setTimeout和setInterval来说很重要。如果对mainloop机制和渲染机制有一定了解的读者来说,上面这几条其实是非常难做到地,哪怕是较为接近理想的结果。

幸运地是,总是有聪明的人来帮助大家解决难题。对问题提出一个漂亮解决方案的是mozilla的Robert O’Callahan。他的灵感和依据来源于CSS。CSS是知道动画什么时候发生,所以能够较为准确的知道什么时候刷新UI。对于JavaScript来说,是不是也可以根据类似的机制呢?答案是肯定地。其做法是增加一个新的方法requestAnimationFrame, 该方法告诉浏览器JavaScript想发起一个动画帧,然后在动画帧绘制之前,需要做一些动作,这样浏览器可以根据需要来优化自己的mainloop机制和调用时间点,以达到较好地平衡效果。

好吧,下面来看看mainloop机制及其工作原理。

 

## 渲染mainloop

因为chromium是多进程的结构(参看Chromium多进程架构篇),所以,跟一般浏览器不一样的是,Browser进程UI用户界面的mainloop和Renderer进程的主线程的mainloop不是同一个,分别位于两个不同的进程,所以UI和渲染可以互相不影响,听起来这好像很不错,是的,但是问题依然存在,那就是Renderer进程的渲染工作和JavaScript的执行工作都在其主线程中,由mainloop来负责调度完成,所以竞争依然存在。

大致过程是一个大的循环加上一个事件队列,具体的过程,如下图所示。当队列中有事件时,从队列中取出第一个事件,设置相应的状态信息,处理该事件及其对应的处理函数,直到该函数处理完后,才重新检查队列中是否有事件。如果有,继续处理;如果没有,则继续等待。这其中可以看出,如果队列中事件多的时候,那么很多事件可能来不及处理,从而造成比较大的延时,因而事件的平均等待时间会比较长。同时,如果事件的处理函数需要的时间很长,就会造成后面的事件一直在等待,同样会增加事件的平均等待时间。而当队列比较空闲时或者事件的处理函数需要的时间比较短,则事件的平均等待时间会相对小很多。


##WebKit和Chromium中的实现

理解了mainloop之后,下面来看一看setTimeout和setInterval的实现。

来看一下它们的实现:WebKit中setTimeout和setInterval的实现机制是类似的,区别在于后者是重复性的,见下图所示的类图关系。

WebKit会为DOM中的每个setTimeout和setInterval的调用创建一个DOMTimer,而后该对象会由存储TLS(thread localstorage)中的ThreadTimers负责管理,其内部其实是一个最小堆,每次取timeout时间最小的,同时,时间相同的Timer可以合并。

当Timer超时后,Chromium清除该Timer对象,同时调用相应的回调函数,回调函数通常会更新页面的样式和布局,这会触发relayout,从而触发立即重新绘制一个新帧。


结合上面的描述,我们大致地总结setTimeout和setInterval主要不足就是:

1.      setTimeout和setInterval从不考虑浏览器内部发生了其他什么事,它只要求浏览器在某个时间之后调用它的回调函数,无论浏览器很繁忙或者页面被隐藏(虽然某些浏览器做了这方面的优化,例如chromium);

2.      setTimeout和setInterval只要求浏览器做什么,而不管浏览器能不能做到(例如mainloop有很多事件需要处理),这有点强人所难,而且会带来极大的资源浪费。举个例子,例如屏幕的刷新率是60HZ,但是设置的时间间隔是5ms,其实对用户来说根本看不到这些变化,但是额外需要消耗更多的CPU资源,太不环保了…

3.      setTimeout和setInterval可能是编程风格方面的考虑。如果每一帧可能在不同的代码出需要设置回调函数,一个方法是统一到一个地方,但是这有点勉为其难,另一个方法是分别用setInterval设置它们,这个方法的问题是,浏览器可能需要计算更多次,刷新更多次的屏幕,唉。

现在再来看看requestAnimationFrame的实现,看看其如何解决这些不足之处的。其原理就是其会申请绘制下一帧,至于什么时候不知道,由浏览器决定,只需要浏览器在绘制下一帧前执行其设置的回调函数,完成JavaScript对动画所做的设置和逻辑即可。基本过程是这样的:

1.      JavaScript调用requestAnimationFrame,因而相应的webkit和chromium会调度一个需要绘制下一证的事件,该事件会将requestAnimationFrame的调用上下文和回调函数记录下来;

2.      上面的请求会触发Chromium更新页面内容的事件,该事件被mainloop调度处理后,会检查是否需要调用动画的相关处理,因为有动画需要处理,所以会依次调用那些回调函数,JavaScript引擎会更新相应的CSS属性或者DOM树修改;

3.      Chromium触发重新计算layout(参看layout章节),更新自己的Renderer树(参看webkit渲染基础章节),而后绘制,完成一帧的渲染。

下图是一个上述过程对应的状态转换图,来源于chromium的官方网站,看着的确比较饶人,可以先理解一下其中几个主要的概念:

Floortime:指的是绘制下一帧之前需要等待的事件间隔

Invalidation:触发重新绘制请求的操作;

scheduleAnimation:JavaScript调用requestAnimationFrame所引起的WebKit内部请求调度动画的操作;

这些状态的转换倒是说明了,requestAnimationFrame可以很好地和Chromium内部的绘制过程结合,从而达到比较好的性能。


为了实现更好的性能,chromium中对requestAnimationFrame有三个设计原则

1.      当页面不可见时,其回调函数不会被调用,这可以减少CPU和GPU的使用率,更环保嘛;

2.      其最大调用频率不会超过60hz,无论屏幕的刷新率是多少,因而回调函数也不会每秒调用超过60次,这是因为60FPS已经能够满足UI流畅的要求了,更频繁的刷新效果不明显;

3.      只有当页面真正开始渲染时,回调函数才会被调用。

为了对比二者的性能上的差异,我测试了GuiMark中HTML5Charting Test benchmark,修改里面一些代码(其缺省使用的是setInterval,改为requestAnimationFrame作对比),从实际测试的效果上看,在Google Chrome中,两者相差不是特别大,使用了requestAnimationFrame的benchmark的FPS大概只好了1~2FPS,所以chrome对timer机制的优化做地应该相当不错。如果你遇到了其他差别比较大的例子,欢迎跟我和大家分享。

Google Chrome对其处理的比较好不代表其他浏览器也是,所以各位还是在编程时候多考虑考虑,多思考思考,为了更好的性能,为了环保…

 

## 设计机制带来的编程考虑

最后,结合mainloop和requestAnimationFrame的设计原理和机制,看一看它们带给我们在编写JavaScript代码时有哪些方面的思考和便利:

1.      回调函数不能太大,不能占用太长时间,否则会影响页面的响应和绘制的频率;

2.      requestAnimationFrame不需要设置间隔时间,不同刷新率的间隔时间不一样,这完全由浏览器来控制,而不需要JavaScript程序员操心;

3.      回调函数无需合并,程序员可以在任意位置设置回调函数,它们可以被浏览器集中处理,而无需要一个统一的入口。

 

## 源文件目录

third_party/WebKit/Source/WebCore/page/

  支持requestAnimationFrame,setTimeout和setInterval的绝大多数基础设施都在这里,建议在该目录下搜索这些关键字即可

third_party/WebKit/Source/WebCore/platform

  Timer方面的一些支持

 

## 参考文献

1.      http://dev.chromium.org/developers/design-documents/requestAnimationFrame-implementation

2.      http://www.cnblogs.com/rubylouvre/archive/2011/08/22/2148793.html

3.      http://www.nczonline.net/blog/2011/05/03/better-javascript-animations-with-requestAnimationFrame/

4.      https://developer.mozilla.org/en-US/docs/DOM/window.requestAnimationFrame

5.      http://www.w3.org/TR/animation-timing/#requestAnimationFrame

6.      http://creativejs.com/resources/requestAnimationFrame/

7.      http://www.craftymind.com/factory/guimark2/HTML5ChartingTest.html

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:144001次
    • 积分:3985
    • 等级:
    • 排名:第8165名
    • 原创:243篇
    • 转载:159篇
    • 译文:0篇
    • 评论:2条
    文章分类
    最新评论