导语:在本文中,我将描述在多标签浏览中出现的问题,并找到对应的解决方案以及Quantum DOM在其中所起的作用。
使用多标签浏览变得越来越普遍,因为人们在Facebook,Twitter,YouTube,Netflix和Google Docs等服务上花费的时间越来越多,甚至已经成为人们日常生活中的一部分。
Quantum DOM:调度是Project Quantum的一个重要功能,其重点是使Firefox操作更加方便,特别是当许多选项卡打开时。在本文中,我将描述在多标签浏览中出现的问题,并找到对应的解决方案以及Quantum DOM在其中所起的作用。
问题1:不同类别的任务优先级排序
由于多处理器Firefox(e10s)在Firefox 48版本中被首次启用,因此Web内容选项卡现在可以在单独的内容进程中运行,以减少给定进程中操作系统资源的拥挤。然而,经过深入的研究,我发现内容进程中主线程的任务队列仍然拥挤了多个任务。内容进程中的任务可能有许多可能的来源,比如,通过IPC(进程间通信)从主进程(例如输入事件,网络数据和vsync)直接进入网页(例如从setTimeout,requestIdleCallback或postMessage),或内容进程内部(例如垃圾收集或遥测任务)。为了更好的响应,我已经学会了为requestIdleCallback和垃圾收集优先处理用户输入和vsync上的任务。
问题2:选项卡之间缺少任务优先级
在Firefox内部,在前台和后台选项卡中运行的任务在单个任务队列中以先到先得的顺序执行。将前台任务优先于后台任务是比较合理的,以提高Firefox用户的用户体验响应能力。
解决方案
来看看我是如何处理这两个安排的挑战的,我会将它们分成一系列导致可实现目标的行动:
1.在类别和标签组的内容进程的主线程上分类和优先处理任务,以提供更好的响应。
2.如果此抢占对用户的运行效率不明显,则抢占运行后台选项卡的任务。
3.由于资源有限,提供了更少的内容进程可供e10s multi选择。
任务分类
为了解决我们的第一个问题,我们将内容进程中主线程的任务队列划分为3个优先级排队:高(用户输入和刷新驱动程序),正常(DOM事件,网络,TimerCallback,WorkerMessage)和低(垃圾收集,IdleCallback)。注意:优先级相同的任务顺序保持不变。
任务分组
在描述第二个问题的解决方案之前,让我们将TabGroup定义为一组通过window.opener和window.parent关联的打开的选项卡。在HTML标准中,这称为相关浏览环境的单位。由于任务是孤立的,如果它们属于不同的TabGroups,则不能互相影响。任务分组确保来自同一TabGroup的任务按顺序运行,同时允许我从背景TabGroups(background TabGroups)中断任务,以便从前台TabGroup运行任务。
在Firefox内部结构中,每个窗口或文档都包含对它所属的TabGroup对象的引用,它提供了一组有用的调度API。这些API使Firefox开发人员更轻松的将任务与特定的TabGroup相关联。
如何将任务分组到Firefox中
以下我会通过几个示例来展示如何在Firefox中的各种类别中分组任务:
1.在window.postMessage()的实现中,一个名为PostMessageEvent的异步任务将被分派到主线程的任务队列中:
随着DOM窗口与TabGroup的新关联以及TabGroup中提供的新调度API,我现在可以将此任务与适当的TabGroup相关联并指定TaskCategory:
2.除了可以与TabGroup相关联的任务之外,内容过程中还有几种任务,如通过垃圾收集进行遥测数据收集和资源管理,与任何Web内容无关,下图就是垃圾收集开始的方式
要使分组没有TabGroup依赖项的任务,引入了一个名为SystemGroup的特殊组。然后,可以修改PokeGC()方法,如下所示:
我们现在已将此GCTimerFired任务分组到具有TaskCategory :: GC指定的SystemGroup,这允许调度程序中断任务以运行任何前台选项卡的任务。
3.在某些情况下,相同的任务可以通过特定的Web内容或具有内容进程中系统特权的内部Firefox脚本来请求。当任务不绑定到任何窗口或文档时,我就必须决定SystemGroup是否适用于请求。例如,在内容进程中实现DNSService时,可以提供可选的TabGroup-versioned事件目标,以便在解析DNS查询后进行结果回调。如果未提供可选事件目标,则将选择TaskCategory :: Network中的SystemGroup事件目标。我会先假设请求是从内部脚本或与任何窗口及文档无关的内部服务触发的。
TabGroup类别
一旦在调度程序中任务分组完成,我们从数据集中分配一个每个选项卡组的协作线程,以便使用TabGroup中的任务。每个协作线程在任何安全点都可以通过JS中断由调度程序预先排除。然后,主线程通过这些协作线程进行虚拟化。
在这种新的协作线程方法中,我会确保一次只能运行一个线程。这将分配更多的CPU运行时间到前台TabGroup,并且还确保Firefox中的内部数据是正确的,其中包括许多服务,管理器和有意设计为单例对象的数据。
任务分组和调度中遇到的障碍
很明显,Quantum-DOM调度的性能高度依赖于任务分组。理想情况下,我期望每个任务只能与一个TabGroup相关联。然而,实际上,一些任务可以同时为多个TabGroups提供服务,这些TabGroups需要提前重构以便支持分组,并且并不是所有的任务都可以在调度程序准备启用之前按时间进行分组。因此,为了在所有任务分组之前积极的启用调度器,当未分组的任务到达时,采用以下设计来临时禁用抢占,因为我不知道该未分组任务属于哪个TabGroup。
任务分组的当前状态
我要感谢来自DOM,Graphic,ImageLib,Media,Layout,Network,Security等各个子模块的许多工程师,他们根据显示的频率帮助清除了这些未分组(未标记)的任务遥测结果。
下表显示了在内容进程中运行任务的遥测记录,这能让你更好地描述Firefox正在运行的任务:
目前超过80%的任务已经清除了任务分组和调度的障碍,但是,仍然有相当数量的匿名任务还未被清除。遥测将有助于检查到达主线程的2个未分组任务之间的平均时间。平均时间越长,利用Quantum-DOM调度程序进行的性能优化就越好。