首页 > 代码库 > Chromium Blink项目最新技术报告和下一步发展方向

Chromium Blink项目最新技术报告和下一步发展方向

摘要:BlinkOn3会议11月份刚在Google的MountainView办公地点举行,本文选取了BlinkOn3的几个话题,如为Blink绘图瘦身的SlimmingPaint,多优先级的Blink调度器,Oilpan垃圾回收机制,用JavaScript开发新的DOM特性等等,和大家一起分享Blink项目目前取得的进展和下一步发展方向。

原创文章,转载请以链接形式注明原始出处为http://blog.csdn.net/hongbomin/article/details/41091679.

Blink最新技术进展

根据BlinkOn3会议讨论的话题,我们大致可以了解到Blink项目的最新进展和下一步的发展方向,总体上来说,优化Blink在移动平台的图形性能和内存消耗方面仍然是目前Blink项目的重中之重,也是Chromium整个项目的重中之重,此外,提升WebApp使用体验、提高代码安全性和健壮性方面以及持续的代码重构,一直以来都备受重视。这里我们选取了其中几个话题,尝试从所要解决的问题及其采用方案的角度对其作简要分析。

Slimming Paint: 将绘图的瘦身行动进行到底

Slimming是减肥的意思,这个词用的很形象。一直以来Blink项目都努力提高绘图的性能,比如引入实现端绘图(--enable-impl-side-painting)技术将页面内容的绘制工作从主线程中解放出来,由Compositor线程将繁重的绘制任务安排在单独的线程中进行,而主线程仅仅需要把页面内容的绘图命令录制成SkPicture,交给Compositor线程就可以了,比起将内容直接绘制成像素,录制命令的开销要小的多,尽管实现端绘图可有效地减轻主线程的负担,但纵观整个Blink的渲染流水线,还存在不少问题。

首先,Blink页面中的渲染层管理问题。Blink一直沿用了早期WebKit硬件加速的页面渲染框架,为了使页面能够按正确的次序显示页面中重叠、半透明、CSS动画等内容,Blink会将页面分层多个RenderLayer构建RenderLayer树,每个RenderObject都属于一个RenderLayer,但一个RenderLayer可能包含多个RenderObject。从页面的合成角度看,出于内存方面的考虑,不是每个RenderLayer都需要自己独立的后端存储,所以便有GraphicsLayer,RenderLayer与GraphicsLayer的关系类似于RenderObject与RenderLayer的关系,也就是,一个RenderLayer必定属于GraphicsLayer,而GraphicsLayer可能包含多个RenderLayer。在页面发生更新时,Blink需要做的事情是遍历整个RenderLayer树,计算出有哪些RenderLayer需要更新,再调用RenderLayer::paint将更新的内容绘制到所属的GraphicsLayer上。不难看出,Blink不仅要计算更新的区域,还要维护RenderLayer树以及GraphicsLayer树。

其次,页面存在潜在的基本合成问题。对于以下的HTML页面,将会得到错误的合成结果:

<html>
<body>
  <canvas id="canvas"style="background:red;width:100px;height:100px"></canvas>
  <div id="overlap"style="background:green;margin-top:-50px;margin-left:50px;width:100px;height:100px"></div>
</body>
</html>

正确的合成结果应该是绿色的矩形叠加在红色的矩形之上。但Blink的输出是,红色的总是在绿色之上,原因就是红色的<canvas>元素有自己独立的RenderLayer,而<div>元素没有独立的RenderLayer,而是与<body>共享同一个RenderLayer,<div>元素本身是不会参与overlap检测的。

Slimming Paint目标就是要重写整个Blink和Chromium的渲染流水线,解决上述提到的两个问题,同时提高命令录制和绘制的性能。这是一次比较大的架构调整,Blink代码关于绘图方面的逻辑会有比较大的改动:

  • Paint操作将会从RenderObject移到一个专门的Painter中执行;
  • RenderLayer和GraphicsLayer将被全部删除,取而代之的PaintList;
  • Compositor将PaintList执行分层算法进行层化(Layerization) 操作;

大致的流程如下图所示:


在新的渲染流水线中,Blink不用再维护RenderLayer和GraphicsLayer,唯一要做的是,通过每个RenderObject的Paint生成整个页面内容的PaintList和为Compositor提供必要的分层提示信息。PaintList类似于DisplayList,包含了绘制次序,绘制命令,变换矩阵,Clip区域等信息,而Compositor从这些信息中完全能够计算出如何对页面进行分层。

Blink Scheduler:优先级轮转的任务队列

Blink Scheduler是由Google伦敦团队主导的一项优化,旨在解决任务队列的“拥堵”问题,简而言之,一些高优先级的任务被一些优先级低、执行时间过长的任务阻塞,导致高优先级的任务延迟执行,从而影响整体的流畅度。

Blink主线程的任务来自系统中各个模块,如下图所示,所有的这些任务都是由主消息循环统一调度。每当向消息循环发布一个任务时, 这个任务会被添加到任务队列的队尾,消息循环会按照先进先出(FIFO)的顺序依次从任务队列中取出一个任务,然后再执行,直到任务队列为空。所以,如果一个高优先级的任务到达消息循环后,它必须等待在它之前所有的任务都执行完毕后才有机会得以执行。


Blink Scheduler通过引入多优先级任务队列,正在尝试解决上述问题。每个任务都有一个优先级,消息循环按照优先级将任务添加到相应的队列中,如果单纯只是对每个任务指定一个优先级,可能带来一个问题就是如何解决任务之间的依赖性,为此,Blink将任务划分为三个类别:1)Compositor;2)Default;3)Idle,其中Compositor任务的优先级最高,Default任务次之,Idle任务优先级最低。按优先级执行任务。然而,如果静态地指定Compositor任务总是最高的,页面加载的性能降低了14%,显然也是不可取的。

所以,Blink Scheduler采用了上下文感知的动态优先级策略,例如,

  • 当用户交互时,Compositor任务更重要,而资源加载任务没有那么重要,提升Compositor任务优先级至最高;
  • 当浏览页面时,资源加载任务变得重要了,而Compositor任务次之,提升Default任务优先级至最高;
  • 当开始新的一帧时,Compositor任务和Default任务具有相同的优先级;

在实现方面,Blink Scheduler对Chromium的base/message_loop底层代码将会有较大的改动,还会在content层次上增加对BlinkScheduler的抽象,实现多优先级任务队列的调度,支持将任务发布到不同的任务队列上,支持选择下一个待处理的任务队列,支持动态修改优先级策略等,如下图所示:


Oilpan:Blink专属的自动垃圾回收器

Oilpan项目引入的背景是,Chromium在浏览网页时,有时会发生莫名奇妙的内存泄露问题。根据Blink的内存泄露检测器(LeakDetector)给出的数据显示,ChromiumWebView在访问946个网站之后,有294个WebCore::Document对象没有得到释放。


在Oilpan项目之前,Blink和Chromium都采用引用计数技术(referencecounting)来管理内存,每个对象内部都一个引用计数,表明当前对象被引用了多少次,当引用技术归零时,对象就会被自动释放掉,这种方式一直以来都存在一个缺陷就是循环引用问题,就A引用了,B又引用了A,最后导致A和B都没有机会释放,此外,C++中启用引用计数还存在其他几个方面的问题:

  • 引用计数器的增减开销问题;
  • C++中可以通过Raw指针轻易地绕开RefPtr的管理,一旦使用不当,将导致use-after-free内存错误,存在安全问题;

尽管引用计数存在上述一些问题,但它很轻量级,仍然是C++程序中广泛使用的自动内存管理计数。Blink项目并不满足这种轻量级的内存管理方法,于是Oilpan项目提上日程,要实现对Blink对象的自动回收机制。比起引用计数技术,Oilpan垃圾回收器确实是个庞然大物,它实现了一个一般只有虚拟机才需要的高级特性,然而Blink项目力求精益求精,追求最好!

Oilpan实现了一种跟踪式的垃圾回收机制,具有如下特点:

  • Blink中所有的对象都将分配在一个受托管的堆中,每个对象都提供了一个trace的方法,用来建立与堆中其他对象的可达关系,因此,从根节点(一般DOMWindow)出发,Blink的对象在托管堆中形成了一个对象图,那些由根节点不可达的对象将会被GC掉,这样就避免了循环引用问题。
  • Oilpan的GC并不会随时都发生,它会被推迟到消息循环中执行,因为当消息循环执行完任务队列中最后一个任务时,此时Blink的栈为空,没有在栈中分配的对象了。
  • 一旦需要执行GC时,Blink首先要确保所有运行的线程到达了一个“安全点”,不会再分配新的对象,然后从根节点出发,计算堆中所有对象的传递可达性,并标记(mark)所有可达的对象,最后每个线程开始清理(sweep)属于自己的那部分堆空间,回收所有未被标记的对象,将其插入到空间列表中。
  • 与V8引擎的GC相比,Oilpan的GC会牵扯到Blink所有的线程,Database线程,File线程等等,所有的线程都共享一个Oilpan的堆空间。
  • Oilpan提供了两个超类GarbageCollected和GarbageCollectedFinalized,来保证它们的子类都分配在由Oilpan管理的堆中。

截止到目前,Oilpan基础框架已经比较稳定,modules/中所有对象默认都启用了Oilpan,但Node层次结构还未正式启用。

关于Oilpan,大家可能比较关心它的性能和内存开销问题,评价Oilpan是否成功有三点,第一,比引用计数更快,第二,不能使性能变差,第三,不会增加内存使用量的峰值。根据最新的性能和内存使用评测结果来看,执行效率和内存使用的峰值都不是问题,但因GC带来的暂停时间是个问题,特别是对于一些动画的benchmark,在Nexus7设备上有些GC操作要花费超过50ms,显然是不能接受的,下一步的方向就是优化stop-the-world所耗费的时间。

ServiceWorker:极大地提升Web App的离线缓存能力

ServiceWorker是一项比较新的Web技术,是Chromium团队在吸收了ChromePackaged App的Event Page机制,同时吸取了HTML5 AppCache标准失败的教训之后,提出一套新的W3C规范,旨在提高WebApp的离线缓存能力,缩小WebApp与NativeApp之间差距。

目前Web App在离线缓存方面并不完善,尽管有HTML5 AppCache标准允许Web开发者通过manifest文件指定WebApp可以被浏览器缓存的文件清单,但由于这套机制不够灵活,存在较大的局限性,被很多Web开发者抱怨。ServiceWorker试图改变这一现状。

ServiceWorker可以看做Web Worker规范的一个有趣的应用,类似于SharedWorker,专为WebApp离线使用而设计的,它是一段可以安装在Web网站上的JavaScript代码,它运行在一个独立的Worker上下文中。当访问网站时,ServiceWorker会监听网络请求,如资源获取,更新等,甚至在离线情况都能收到这些事件,Web开发者可以决定在ServiceWorker的代码中如何处理这些事件。

ServiceWorker一个典型的应用情景是,当访问一个网站时,在有可用网络的情况下,浏览器从服务器端下载资源,将网页显示给用户,但在网络不可用的情况,即离线状态,如果再次访问这个网站,那么浏览器会报告404错误,有了ServiceWorker,情形就好的多了,ServiceWorker可以监听网络请求事件,一旦发现当前处于离线状态,ServiceWorker会转而从缓存中读取一些内存展现给用户,让用户即时在离线状态下,也可以使用网站的服务,大大增强了Web页面的离线能力。

安装了ServiceWorker的页面称为“受控页面”,ServiceWorker是浏览器到服务器端之间的一个中间层,可以转接所有的网络请求,包括XMLHttpRequest请求加载资源,也可以创建和调出所有存储在本地缓存的数据,ServiceWorker和受控页面之间还可以相互发送消息。

Blink-in-JavaScript: 用JavaScript实现DOM的新特性

用C++实现过DOM API的同学应该都有亲身体会,为Blink新增加一个DOM特性确实是件比较繁琐的事情,为了实现一个DOM新特性,你需要修改IDL,IDL解释器会生成一堆C++代码,然后还得用C++根据固定的模式编写具体特性的实现代码。众所周知,C++代码难写,需要仔细的考虑每个对象的生命周期,稍有不慎,内存问题最终会导致安全问题,这样导致开发效率低下,而且日后维护成本也大。

Blink-in-JS使Blink开发者使用JavaScript实现DOM新特性成为可能,其基本思想就是用C++实现DOM的核心部分导出一组JSAPI作为基础库,基于这些已有的JavaScriptAPI,尽可能使用JavaScript实现DOM的其他部分,例如XSLT,editingAPI,DOMWindow.atob/btoa以及ScriptRegexp等。以XSLT为例,Blink的C++实现很复杂,但实际上用的并不多,主要是一些企业级用户,对于这样一类不是很受欢迎或者很快将会过时的API,Blink-in-JS可以将复杂的C++实现从Blink代码库移除掉,取而代之使用JavaScript去实现这类API。

Blink-in-JS可以帮助Blink开发者提升开发效率以及代码的可维护性,但副作用有可能是导致那些用JS实现的API运行时性能和内存开销比C++的要多,所以,对于性能要求较高的API,建议还是用C++实现更好。对于内存开销问题,被JIT过的JS代码大小是C++二进制代码的20倍之多,相应的解决办法是,启用JavaScript代码的延迟编译,只有当app请求调用这个特性时,才开始编译它的JavaScript代码,而且编译过的JS代码使用完了之后会被丢弃掉,下次请求时需要再重新编译,这实际上只是折中方案。

以DOMWindow.atob为例,Blink-in-JS的编程模型如下:

// WindowBase64.idl
interface WindowBase64 {
 [ImplementedInJS]DOMString atob(DOMString str);
};
// WindowBase64.js
installClass(“WindowBase64”, function() {
 return {atob:function atob(str) {
 // Here |this| isequal to |window|.
 returnbase64Encode(str);
 }};
});

安全模型是设计和实现Blink-in-JS时一个非常关键的考虑因素,Blink既要保证Blink-in-JS和应用层JavaScript代码能够操作相同的DOM对象,又要保证WebApp的JavaScript代码不能访问Blink-in-JS中的JS对象。Blink-in-JS借鉴了ChromeExtension的安全模型机制,也使用了V8引擎中“world”隔离Blink-in-JS和应用层JavaScript代码的运行环境。在ChromeExtension中,World提供了完全隔离的JS运行环境,但不同的World之间可以通过DOMWrapper共享所有的DOM对象,如下图所示,


所以,理论上Blink-In-JS可以达到和Chrome Extension相同的安全级别。

目前Blink项目的状态

在过去半年时间内,Blink项目演进速度还是相当快的,仅从7月份到9月份,代码行数从264万增加到269万,每天平均有52个commit,Blink社区共收到151个Intents的贡献请求,其中Google占了51%,剩余的来自整个开源社区,这个数字说明Blink已经发展的相当不错,社区的参与积极性还是比较高的。

截止到11月份,Blink已经落地的特性有:


还未落地的特性有:


总结

本文简要地介绍了目前Blink项目中几个比较重要的子项目,比如为了进一步提升渲染性能而重写整个Blink/Chromium渲染流水线,为Blink绘图瘦身的SlimmingPaint技术,优化高优先级任务处理的BlinkScheduler,还有解决不明内存泄露问题的Oilpan自动垃圾回收器,还有Blink-in-JS实现了开发效率和代码安全的双提升,以及ServiceWorker能够灵活地支持WebApp离线能力,除了上面提到的,还有一些没有特别做介绍,但对后续Blink项目演进有比较大影响的变化,例如,和Chromium代码库的合并,如果只是文件目录的合并倒也没有太大变化,但如果是源码级别的合并,改动就大了,包括合并base和WTF基础库等,还有ProjectWarden项目成立的初衷就是专门负责重构Blink代码,简化排版和绘图的流程,清理Blink中各种诡异的问题,如Repaint-after-layout, 排版过程中不能修改RenderTree等。这些变化已经发生或者正在发生,使Blink项目得以快速向前演进,下一步技术方向仍然是以性能为主,尤其是针对移动设备的,以构建WebApp平台为基本出发点,逐步缩小与NativeApp的差距,与此同时,通过代码重构和架构调整,提高Blink代码的可维护性、可靠性和安全性,从而提高开发效率。

注:本文所有图片均来自BlinkOn会议的Slides,版权归原作者所有。

原创文章,转载请以链接形式注明原始出处http://blog.csdn.net/hongbomin/article/details/41091679.

相关链接

BlinkOn2所有PPT资源汇总:http://bit.ly/blinkon2

BlinkOn3所有PPT资源汇总:http://bit.ly/blinkon3


Chromium Blink项目最新技术报告和下一步发展方向