牛骨文教育服务平台(让学习变的简单)
博文笔记

一个超级快速的CSS引擎:Quantum CSS(又名Stylo)

创建时间:2017-09-06 投稿人: ....... 浏览次数:301

您可能已经听说过Project Quantum ...它是Firefox的内部部件的重大改写,使Firefox快速。我们正在从我们的实验浏览器Servo中进行部分交换,并对引擎的其他部分进行了大量改进。

该项目已经与喷气式飞机仍在飞行中替换喷气发动机相比较。我们正在按组件进行更改,以便在每个组件准备就绪后,您可以在Firefox中看到效果。

一个超级快速的CSS引擎:Quantum CSS(又名Stylo)

而Servo的第一个主要组件 - 名为Quantum CSS(以前称为Stylo)的新CSS引擎 - 现在可以在我们的Nightly版本中进行测试。您可以通过转到about:config并设置layout.css.servo.enabled为true 来确保它已打开。

这款新引擎将来自四个不同浏览器的最先进的创新融合在一起,以创建一个新的超级CSS引擎。

一个超级快速的CSS引擎:Quantum CSS(又名Stylo)

它利用了现代硬件,并行化了机器中所有内核的工作。这意味着它最多可以运行2或4甚至18倍。

除此之外,它结合了来自其他浏览器的现有最新优化。所以即使没有并行运行,它仍然是一个快速的CSS引擎。

一个超级快速的CSS引擎:Quantum CSS(又名Stylo)

但是什么是CSS引擎呢?首先,我们来看看CSS引擎,以及它如何适应浏览器的其余部分。那么我们可以看看Quantum CSS如何使它更快。

什么是CSS引擎?

CSS引擎是浏览器渲染引擎的一部分。渲染引擎采用网站的HTML和CSS文件,并将其转换为屏幕上的像素。

一个超级快速的CSS引擎:Quantum CSS(又名Stylo)

每个浏览器都有一个渲染引擎。在Chrome中,它叫做Blink。在Edge中,它被称为EdgeHTML。在Safari中,它被称为WebKit。在Firefox中,它被称为Gecko。

要从文件到像素,所有这些渲染引擎基本上都做同样的事情:

  1. 将文件解析为浏览器可以理解的对象,包括DOM。在这一点上,DOM知道页面的结构。它知道元素之间的父/子关系。不知道这些元素应该是什么样的。

  2. 找出这些元素应该是什么样的。对于每个DOM节点,CSS引擎都会确定哪些CSS规则适用。然后,它会为该DOM节点的每个CSS属性确定值。

一个超级快速的CSS引擎:Quantum CSS(又名Stylo)

一个超级快速的CSS引擎:Quantum CSS(又名Stylo)

一个超级快速的CSS引擎:Quantum CSS(又名Stylo)

  1. 绘出每个节点的尺寸及其在屏幕上的位置。将为屏幕上显示的每个东西创建框。这些方框不仅仅代表DOM节点,还可以为DOM节点内的东西,如文本行。

一个超级快速的CSS引擎:Quantum CSS(又名Stylo)

  1. 画不同的盒子。这可能发生在多层。我想起了这个像老式的手绘动画,带有洋葱的纸层。这样就可以改变一个层,而不必在其他层上重绘某些东西。

  2. 拿这些不同的绘图层,应用任何只有合成器的属性,如transforms,并将它们变成一个图像。这基本上就像是将层叠在一起的图片。此图像将在屏幕上呈现。

这意味着当它开始计算样式时,CSS引擎有两件事情:

  • 一个DOM树

  • 风格规则清单

它逐个浏览每个DOM节点,并计算出该DOM节点的样式。作为其中的一部分,它为DOM节点提供了每个CSS属性的值,即使样式表未声明该属性的值。

我想像这样像一个人通过填写一个表单。他们需要为每个DOM节点填写这些表单之一。而对于每个表格领域,他们需要有一个答案。

要做到这一点,CSS引擎需要做两件事情:

  • 找出哪些规则适用于节点 - aka 选择器匹配

  • 使用父级值或默认值(也称为级联)填充任何缺少的值

选择器匹配

对于此步骤,我们将添加与DOM节点匹配的任何规则到列表。因为多个规则可以匹配,所以同一属性可能有多个声明。

此外,浏览器本身还添加了一些默认的CSS(称为用户代理样式表)。CSS引擎知道要选择哪个值?

这就是特定性规则的出现。CSS引擎基本上创建一个电子表格。然后它根据不同的列对声明进行排序。

具有最高特异性的规则胜出。所以基于这个电子表格,CSS引擎填写了它可以的值。

对于其余的,我们将使用级联。

级联

级联使得CSS易于编写和维护。因为级联,可以设置color在机身性能和知道该文本p,并spanli元素都将使用该颜色(除非你有一个更具体的覆盖)。

为此,CSS引擎会查看其表单上的空白框。如果该属性默认继承,那么CSS引擎将向上爬树,以查看其中一个祖先是否具有值。如果没有一个祖先有一个值,或者该属性不继承,它将获得一个默认值。

所以现在所有的样式都已经为这个DOM节点计算了。

一个sidenote:风格结构共享

我向你展示的形式有点虚伪。CSS有数以百计的属性。如果CSS引擎持有每个DOM节点的每个属性的值,它将很快耗尽内存。

相反,引擎通常会做一些所谓的样式结构共享。它们通常在一起被称为样式结构的不同对象中存储数据(如字体属性)。然后,代替在同一对象中拥有所有属性,计算的样式对象只有指针。对于每个类别,都有一个指向具有该DOM节点正确值的样式结构的指针。

这将节省内存和时间。具有类似属性的节点(如兄弟姐妹)可以指向与它们共享的属性相同的结构体。并且因为许多属性都被继承,所以祖先可以与没有指定自己的覆盖的任何后代共享一个结构体。

现在,我们如何做到这一点呢?

那么当你没有优化它时,这就是什么样的计算。

这里有很多工作。它不仅仅需要在第一页的加载中发生。当用户与页面进行交互,将鼠标悬停在元素上或对DOM进行更改时,会重复出现,从而触发重新启动。

这意味着CSS样式计算是优化的最佳选择...而浏览器已经测试出不同的策略,以优化它在过去20年。什么Quantum CSS做的是从不同的引擎中充分利用这些策略,并结合它们来创建一个超快的新引擎。

所以我们来看看这些工作如何共同的细节。

并行运行

Servo项目(Quantum CSS来自)是一个实验浏览器,它试图并行化渲染网页的所有不同部分。这意味着什么?

一台电脑就像一只大脑。有一部分做这个想法(ALU)。在这个附近,有一些短期记忆(寄存器)。这些在CPU上组合在一起。那么长期的内存是RAM。

早期的电脑一次只能用这种CPU来思考一件事情。但是在过去十年中,CPU已经转移到拥有多个ALU和寄存器,分组在一起。这意味着CPU可以一次思考多个东西 - 并行。

Quantum CSS通过分散不同核心上的不同DOM节点的样式计算来利用计算机的最新功能。

这似乎是一件容易的事情,只需拆分树的分支,并在不同的核心上进行。这实际上比这更难,原因很多。一个原因是DOM树往往是不均匀的。这意味着一个核心将比其他核心更多的工作要做。

为了平衡工作,Quantum CSS使用了一种称为工作窃取的技术。正在处理DOM节点时,代码将其直接子代并将其分解为1个或多个“工作单元”。这些工作单位放在队列中。

当一个核心完成了队列中的工作时,它可以在其他队列中查找更多的工作。这意味着我们可以平均分配工作,而不用花时间去走树,找出如何提前平衡。

在大多数浏览器中,很难得到这个权利。并行是一个已知的困难问题,CSS引擎非常复杂。它也坐在渲染引擎的另外两个最复杂的部分之间 - DOM和布局。所以很容易引入一个bug,并行性可能会导致很难追踪的错误,称为数据竞争。我在另一篇文章中更多地解释了这些错误。

如果您接受来自数百或数千名工程师的贡献,您如何并行编程而不用担心?这就是我们有Rust的。

使用Rust,您可以静态地验证您没有数据竞争。这意味着您只需不要让他们进入您的代码,避免棘手的调试错误。编译器不会让你这样做。我将在以后的文章中写下更多关于这一点。在此期间,您可以观看关于Rust中的并行性的介绍视频,或者更深入地讨论工作窃取。

有了这个CSS样式计算就会变成一个令人尴尬的并行问题 - 几乎没有什么可以让你有效地并行运行它。这意味着我们可以接近线性加速。如果您的机器上有4个内核,那么它的运行速度将提高近4倍。

用规则树加速重新开始

对于每个DOM节点,CSS引擎需要完成所有规则以进行选择器匹配。对于大多数节点,这种匹配可能不会很频繁地变化。例如,如果用户将鼠标悬停在父项上,则与之匹配的规则可能会更改。我们仍然需要重新计算其后代的样式来处理属性继承,但是与这些后代匹配的规则可能不会改变。

如果我们可以记下哪些规则与这些后代相匹配,那么这将是很好的,所以我们不必再为它们进行选择器匹配,这就是Firefox以前的CSS引擎借来的规则树。

CSS引擎将通过查找匹配的选择器的过程,然后通过特异性对它们进行排序。从此,它创建了一个链接的规则列表。

该列表将被添加到树中。

CSS引擎尝试将树中的分支数保持在最小。为此,它将尽可能地重用一个分支。

如果列表中的大多数选择器与现有分支相同,则它将遵循相同的路径。但是它可能会到达列表中的下一个规则不在树的这个分支中。只有在这一点上它才会添加一个新的分支。

DOM节点将获得指向最后插入的规则的指针(在本示例中为div#warning规则)。这是最具体的一个。

在restyle上,引擎会快速检查以查看父母的更改是否可能改变与孩子相匹配的规则。如果没有,则对于任何后代,引擎可以跟随后代节点上的指针来获得该规则。从那里,它可以将树跟随到根,以获得匹配规则的完整列表,从最具体到最不具体。这意味着它可以完全跳过选择器匹配和排序。

因此,这有助于减少重建期间所需的工作。但是,在初始设计时,还是有很多工作。如果您有10,000个节点,您仍然需要选择器匹配10,000次。但还有另一种加速方式。

加快样式共享缓存的初始渲染(和级联)

想想一个具有数千个节点的页面。许多节点将匹配相同的规则。例如,想想一个漫长的维基百科页面...主要内容区域中的段落应该完全匹配完全相同的规则,并且具有完全相同的计算样式。

如果没有优化,则CSS引擎必须单独匹配每个段落的选择器和计算样式。但是,如果有一种方式来证明样式将从段落到段落相同,则引擎可以只做一次工作,并将每个段落节点指向相同的计算方式。

这就是Safari和Chrome浏览器启发的风格共享缓存。完成处理节点后,将计算的样式放入缓存中。然后,在开始下一个节点的计算样式之前,它会运行一些检查来查看是否可以使用缓存中的某些内容。

这些支票是:

  • 2节点是否具有相同的ids,类等?如果是这样,那么他们会匹配相同的规则。

  • 对于任何不是基于选择器的内联样式,例如,节点是否具有相同的值?如果是这样,那么上面的规则也不会被覆盖,也不会被同样的覆盖。

  • 父母双方指向相同的计算样式对象吗?如果是这样,那么继承的值也将是一样的。

这些检查一开始就在早期的风格共享缓存中。但是还有很多其他样式可能不匹配的小案例。例如,如果CSS规则使用:first-child选择器,则两个段落可能不匹配,即使上面的检查表明它们应该是。

在WebKit和Blink中,样式共享缓存将在这些情况下放弃,而不使用缓存。随着越来越多的网站使用这些现代选择器,优化越来越有用,所以Blink团队最近删除了它。但事实证明,风格共享缓存有一种方法来跟上这些变化。

在Quantum CSS中,我们收集所有这些奇怪的选择器,并检查它们是否适用于DOM节点。然后我们将答案存储为1和0。如果两个元素具有相同的一个和零个,我们知道它们绝对匹配。

如果DOM节点可以共享已经计算的样式,则可以跳过几乎所有的工作。因为页面通常具有多个具有相同样式的DOM节点,所以这种风格共享缓存可以节省内存并且还能够加快速度。

声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。