摘要:它是對于內(nèi)部的一個(gè)重大改寫,以達(dá)到讓更快運(yùn)行的目的。擁有最高特異性的規(guī)則將會(huì)勝出。將來自于不同引擎的各種策略結(jié)合在一起,從而創(chuàng)造出一個(gè)超級(jí)快的新引擎。為了更平均的分配這些工作,使用了一個(gè)稱之為工作竊取的技術(shù)。
本文轉(zhuǎn)載自:眾成翻譯
譯者:Mactavish
鏈接:http://www.zcfy.cc/article/4041
原文:https://hacks.mozilla.org/2017/08/inside-a-super-fast-css-engine-quantum-css-aka-stylo
或許你聽說過 Quantum 項(xiàng)目。 它是對于 Firefox 內(nèi)部的一個(gè)重大改寫,以達(dá)到讓 Firefox 更快運(yùn)行的目的。我們將實(shí)驗(yàn)性的瀏覽器 Servo 的一部分功能調(diào)換出來,并對引擎的其他部分做除了重大的改進(jìn)。
這個(gè)項(xiàng)目好比一架正在飛行的飛機(jī)的引擎。我們對適當(dāng)?shù)牡胤竭M(jìn)行改進(jìn),一個(gè)一個(gè)組件地改進(jìn), 當(dāng)著這些組件準(zhǔn)備好的時(shí)候,你就能夠看到它對 Firefox 的影響。
第一個(gè)來自 Servo 的主要組件就是一個(gè)全新的CSS 引擎,名為 Quantum CSS (之前稱作 Stylo) — 現(xiàn)在在瀏覽器 Nightly 版本中已經(jīng)可以用于測試了。你可以進(jìn)入about:config 并設(shè)置 layout.css.servo.enabled 以確保這個(gè)功能可以被使用。
這個(gè)新引擎將四個(gè)瀏覽器中最先進(jìn)的革新技術(shù)結(jié)合在一起,創(chuàng)造出了這個(gè)超級(jí) CSS 引擎。
它充分利用了現(xiàn)代的計(jì)算機(jī)硬件,使你的計(jì)算機(jī)的所有核心并行工作。這意味著它比原來快2倍,4倍甚至18倍。
另外, 它結(jié)合了現(xiàn)有的其他瀏覽器的最先進(jìn)的優(yōu)化方式。 所以即使它不是并行運(yùn)行,它依舊是一個(gè)非常迅捷的 CSS 引擎。
但是 CSS 引擎是做什么的呢?首先,讓我們看看 CSS 引擎是如何融入其他瀏覽器的。然后我們再來看 Quantum CSS 是如何做到更快的。
CSS 引擎的作用是什么?CSS 引擎是瀏覽器渲染引擎的一部分。渲染引擎將網(wǎng)站的 HMTL 和 CSS 文件渲染成屏幕上對應(yīng)的像素。
每個(gè)瀏覽器都有一個(gè)渲染引擎。在 Chrome 中它叫做 Blink,在 Edge 中它叫做 EdgeHTML, 在 Safari 中 它叫做 WebKit,在 Firefox 中它叫做 Gecko。
為了轉(zhuǎn)化這些文件成為像素點(diǎn),所有的這些渲染引擎都會(huì)做這些相同的事情:
解析這些文件成瀏覽器能夠理解的對象,包括 DOM。在這一點(diǎn)上, DOM 知道這個(gè)頁面的結(jié)構(gòu)。它知道元素之間的父子關(guān)系。但是它不知道這些元素該是什么樣子。
為了弄清楚這些元素究竟該長什么樣,對于每個(gè) DOM 節(jié)點(diǎn),CSS 引擎會(huì)計(jì)算出要應(yīng)用哪些 CSS 規(guī)則,然后計(jì)算出那個(gè) DOM 節(jié)點(diǎn)應(yīng)用的每個(gè) CSS 屬性的值。
計(jì)算出每個(gè)節(jié)點(diǎn)的大小以及它在屏幕上的位置。 對要出現(xiàn)在屏幕上的東西創(chuàng)建它們所屬的盒子。盒子不僅僅代表 DOM 節(jié)點(diǎn),也會(huì)有在 DOM 節(jié)點(diǎn)內(nèi)部的盒子,比如文本行。
繪制這些不同的盒子,繪制可以發(fā)生在不同的層上。我覺得這個(gè)有點(diǎn)像過去用洋蔥皮紙上的手繪動(dòng)畫。這使得瀏覽器可以只切換一個(gè)層而不用在其他層上重新繪制。
把這些不同的繪制的層,應(yīng)用任何像transform 這樣的合成屬性,然后把他們變成一張圖像。這基本上就像是給這些疊在一起的層拍一張照,這張圖像之后就會(huì)被渲染到屏幕上。
這意味著當(dāng)渲染引擎開始計(jì)算樣式,CSS 引擎有兩個(gè)東西:
DOM 樹
一張樣式規(guī)則的清單
它將遍歷每個(gè) DOM 節(jié)點(diǎn),然后計(jì)算出對應(yīng) DOM 節(jié)點(diǎn)的樣式。對于這部分,它對當(dāng)前 DOM 節(jié)點(diǎn)的每個(gè) CSS 屬性都給予一個(gè)值,哪怕樣式表沒有對這個(gè)屬性聲明一個(gè)值。
I think of it kind of like somebody going through and filling out a form. They need to fill out one of these forms for each DOM node. And for each form field, they need to have an answer.
我覺得這好某個(gè)人去填一張表單。他需要為每個(gè) DOM 節(jié)點(diǎn)都填寫一張表單,然后表單的每個(gè)域都要填上最終的答案。
為了做到這一點(diǎn),CSS 引擎需要做兩件事:
計(jì)算出當(dāng)前節(jié)點(diǎn)需要應(yīng)用哪些規(guī)則?,又叫做 選擇器匹配
為任何空缺的值填補(bǔ)上父元素的值或者是默認(rèn)值,又叫做 層疊
選擇器匹配對于這一步, 我們將任何匹配當(dāng)前 DOM 節(jié)點(diǎn)的規(guī)則添加到一個(gè)列表,因?yàn)榭梢云ヅ涠鄠€(gè)規(guī)則,對于同個(gè)屬性也可能會(huì)有多次聲明。
另外,瀏覽器本身也會(huì)添加一些默認(rèn) CSS (稱作 user agent style sheets)。那么 CSS 引擎怎么知道要選擇哪個(gè)值呢?
這時(shí)候特異性規(guī)則就出場了。CSS 引擎基本上會(huì)創(chuàng)建一個(gè)試算表。然后它會(huì)基于不同列分出不同的聲明。
擁有最高特異性的規(guī)則將會(huì)勝出。所以根據(jù)這張表,CSS 引擎會(huì)應(yīng)用上它能應(yīng)用的值。
其他的, 我們會(huì)用到層疊。
層疊層疊讓 CSS 更易于書寫和維護(hù)。因?yàn)閷盈B,你可以在 body 上設(shè)置 color 屬性,然后你就知道 p元素和 span 元素以及 li 元素都使用那個(gè)顏色 (除非你有更多具體的樣式覆蓋)。
為了做到這點(diǎn),CSS 引擎會(huì)查看樣式表單中空的盒子。如果這個(gè)屬性默認(rèn)是繼承的,那么 CSS 引擎就會(huì)向樹上查找是否有一個(gè)祖先節(jié)點(diǎn)有值。如果沒有任何祖先節(jié)點(diǎn)有這個(gè)值,或者這個(gè)屬性沒有繼承,那么這個(gè)屬性就會(huì)得到一個(gè)默認(rèn)值。
所以現(xiàn)在這個(gè) DOM 節(jié)點(diǎn)所有的樣式都已經(jīng)計(jì)算好了。
旁注: 樣式結(jié)構(gòu)共享剛剛那個(gè)展現(xiàn)給你們的表單其實(shí)是有一些曲解的。CSS 有上百個(gè)屬性。如果 CSS 引擎保持著每個(gè) DOM 節(jié)點(diǎn)的每個(gè)屬性,那內(nèi)存早就不夠用了。
反而, 引擎實(shí)際上干的事情,叫做樣式結(jié)構(gòu)共享。他們將有關(guān)聯(lián)的數(shù)據(jù)(比如字體屬性)存到不同的對象上,叫做樣式結(jié)構(gòu)。然后,計(jì)算出的樣式只是通過指針指向具體的樣式對象,而不是把所有的屬性都放在相同的對象上。對于每種屬性,都有一個(gè)指針指向擁有對應(yīng) DOM 節(jié)點(diǎn)樣式的值的樣式結(jié)構(gòu)。
這樣既節(jié)省了內(nèi)存又節(jié)省了時(shí)間。 擁有相似屬性的節(jié)點(diǎn)(比如兄弟節(jié)點(diǎn))只是指向他們相同的結(jié)構(gòu)并共享那些屬性。同時(shí)又因?yàn)樵S多屬性都是繼承的,所以的祖先節(jié)點(diǎn)可以和任何不指定具有自己重寫屬性的后代節(jié)點(diǎn)共享同一個(gè)結(jié)構(gòu)。
現(xiàn)在,我們怎么樣讓它變得更快?這就是沒有優(yōu)化過的樣式計(jì)算看起來的樣子。
瀏覽器在樣式計(jì)算里做了很多事情。 這個(gè)過程并不只是發(fā)送在頁面第一次加載的時(shí)候。隨著用戶和頁面的不斷交互,這個(gè)過程在不斷地重復(fù),無論是將鼠標(biāo)懸停在元素之上還是改變 DOM 結(jié)構(gòu)都會(huì)觸發(fā)樣式的改變
這意味著 CSS 樣式計(jì)算是實(shí)現(xiàn)優(yōu)化的重要選項(xiàng)。在過去的20年內(nèi),瀏覽器一直在嘗試各種的優(yōu)化策略。Quantum CSS 將來自于不同引擎的各種策略結(jié)合在一起,從而創(chuàng)造出一個(gè)超級(jí)快的新引擎。
那么現(xiàn)在就讓我們來看一下他們是如何一起發(fā)揮作用的。
所有的運(yùn)行都是并行的Servo 項(xiàng)目 (也就是 Quantum CSS 的起源) 的內(nèi)容是使一個(gè)實(shí)驗(yàn)性的瀏覽器將頁面上所有不同部分都并行渲染。這意味著什么呢?
計(jì)算機(jī)就像人類的大腦。有一個(gè)專門用于思考的部分——算數(shù)邏輯單元(ALU)??拷@部分,有一些用于儲(chǔ)存短期記憶——寄存器(register)。他們共同組成了 CPU 。然后還有一些用于儲(chǔ)存長期記憶,也就是 RAM 。
早期使用這樣的 CPU 的電腦一次只能處理一件事情。但是經(jīng)過近十年的發(fā)展,CPU 已經(jīng)進(jìn)化成可以擁有由多個(gè) ALU 和寄存器組合成的核心。這意味著 CPU 可以一次并行處理多件事情。
Quantum CSS 利用了當(dāng)今電腦最新的這些特性將不同 DOM 節(jié)點(diǎn)的樣式計(jì)算分配給不同的核心。
或許這看起來是一件非常簡單的事情,僅僅是將樹的分支分開在不同的核心上處理。因?yàn)槟承┰?,所以?shí)際上卻比想象中的要困難很多。其中之一就是 DOM 樹通常是不平衡的。這意味著可能某個(gè)核心的工作量要比其他的核心要多很多。
為了更平均的分配這些工作,Quantum CSS 使用了一個(gè)稱之為工作竊?。╳ork stealing)的技術(shù)。處理一個(gè) DOM 節(jié)點(diǎn)時(shí),代碼會(huì)獲取他的直接子元素,然后將他們分為一個(gè)或多個(gè) “工作單元”。然后這些工作單元會(huì)被放進(jìn)一個(gè)隊(duì)列之中。
一旦其中一個(gè)核心完成了它當(dāng)前隊(duì)列中的任務(wù),那么他就會(huì)從其他的隊(duì)列中去尋找新的任務(wù)。這意味著我們不必提前遍歷整棵樹去計(jì)算他們的平均任務(wù)就可以均勻地分配任務(wù)。
在大多數(shù)的瀏覽器之中,很難保證這個(gè)方法的正確性。并行性是眾所周知的難題,而 CSS 引擎又十分復(fù)雜。 恰好它又處于渲染引擎中的另外兩個(gè)非常復(fù)雜的部分—— DOM 和布局之間。所以它很容易產(chǎn)生 bug,而且因?yàn)椴⑿行运a(chǎn)生的叫做數(shù)據(jù)競爭的 bug 難以追蹤。我會(huì)在 另一篇文章中闡述更多這類 bug。
如果你的程序接受了來自成百上千的工程師的辛勤奉獻(xiàn),如何讓你的程序不怕在并行環(huán)境從運(yùn)行呢?這就是 Rust 的意義所在。
有了 Rust, 你就可以靜態(tài)地驗(yàn)證以確保沒有數(shù)據(jù)競爭。這意味著通過提前防止難以調(diào)試的 bug 寫入你的代碼之中,你可以避免這些難以調(diào)試的 bug。而編譯器是不會(huì)讓你這么做的。將來我會(huì)撰寫更多關(guān)于這個(gè)內(nèi)容的文章。與此同時(shí),你可以觀看這個(gè)視頻 intro video about parallelism in Rust 或者這個(gè)視頻 more in-depth talk about work stealing。
有了這個(gè),CSS 樣式計(jì)算變成了一個(gè)所謂的尷尬的并行問題——很少有東西會(huì)阻止你在并行中更高效地運(yùn)行。這意味著我們可以得到接近線性的速度提升。假如在你的電腦上有四個(gè)核心,那么它會(huì)以接近原來四倍的速度運(yùn)行。
通過規(guī)則樹來加快樣式重置對于每個(gè) DOM 節(jié)點(diǎn), 都需要CSS 引擎去遍歷所有的規(guī)則去實(shí)現(xiàn)選擇器匹配。對于大多數(shù)的節(jié)點(diǎn),這個(gè)匹配很大程度上不會(huì)經(jīng)常發(fā)生變化。比如,當(dāng)用戶把鼠標(biāo)懸停在一個(gè)父元素上,匹配的規(guī)則或許會(huì)發(fā)生變化。但是我們?nèi)匀恍枰獮樗械暮蟠刂匦掠?jì)算樣式來處理屬性繼承,然而匹配規(guī)則的后代元素很有可能不會(huì)發(fā)生任何變化。
如果我們可以為這些匹配到的后代元素這個(gè)記錄就好了,這樣我們就不用對他們再進(jìn)行選擇器匹配了。這就是所謂的規(guī)則樹——從 Firefox 的上一代 CSS 引擎 — does 中借來。
CSS 引擎會(huì)通過這個(gè)過程計(jì)算出需要匹配的選擇器,并通過特異性將他們分類出來。通過這個(gè)方式,就創(chuàng)建了鏈接的規(guī)則列表。
這個(gè)列表將會(huì)被添加到樹中。
CSS 引擎會(huì)嘗試保存最少分支的樹。為了做到這一點(diǎn),它會(huì)盡量嘗試復(fù)用分支。
如果在列表中的大多數(shù)選擇器和已有的分支相同,那么它會(huì)沿用同樣的路徑。但是它有可能會(huì)遇到這種情況——列表中的下一條規(guī)則并不在當(dāng)前樹的分支中,只有在這種情況下它才會(huì)添加一個(gè)新的分支。
DOM 節(jié)點(diǎn)會(huì)得到指向最后被插入的規(guī)則的指針(在這個(gè)例子當(dāng)中,就是 div#warning 規(guī)則)。這是最這是最特殊的地方。
關(guān)于樣式重置,引擎會(huì)做一次快速檢查,去檢查父元素上的改變是否會(huì)潛在地改變子元素上匹配的規(guī)則。 如果不是,那么對于任何的后代元素,引擎可以通過后代元素上的指針去獲取那條規(guī)則。從這里,它能夠順著樹回到根節(jié)點(diǎn)以獲取完整的規(guī)則匹配的列表,從最具體的到最不具體的。這意味著它能夠完全跳過選擇器匹配和排序。
這個(gè)可以大大減少在樣式重置期間的工作。但是在初始化樣式的時(shí)候仍然需要很多工作。如果你有10,000 個(gè)節(jié)點(diǎn),你仍然需要進(jìn)行 10,000 選擇器匹配。但是也有其他的方式去加速這個(gè)過程。
通過樣式緩存共享加速初始渲染 (以及層疊)試想一個(gè)擁有上千個(gè)節(jié)點(diǎn)的頁面,許多節(jié)點(diǎn)都會(huì)匹配同樣的規(guī)則。比如,一個(gè)很長的維基百科的頁面。 在主內(nèi)容區(qū)域的段落都最終會(huì)匹配相同的規(guī)則,擁有同樣的計(jì)算后的樣式。
如果不進(jìn)行優(yōu)化, CSS 引擎就不得不為每個(gè)多帶帶的段落進(jìn)行選擇器匹配和樣式計(jì)算。但是如果有一種方法能夠證明這個(gè)樣式在段落與段落之間都是相同的,那么引擎就可以只做一次運(yùn)算,并將每個(gè)段落節(jié)點(diǎn)都指向同樣的計(jì)算樣式。
這就是所謂的樣式緩存共享 —— 被 Safari 和 Chrome—does 所啟發(fā)。當(dāng)引擎處理完一個(gè)節(jié)點(diǎn)時(shí),計(jì)算樣式會(huì)被放入緩存中。然后,在引擎開始計(jì)算下一個(gè)節(jié)點(diǎn)的樣式之前,它會(huì)運(yùn)行一些檢查,檢測是否有可用的緩存。
這些檢查是:
兩個(gè)節(jié)點(diǎn)是否擁有相同的 id, 類名, 或者其他?如果是,那么他們會(huì)匹配到相同的規(guī)則。
對于所有那些不是基于選擇器的——內(nèi)聯(lián)樣式,引擎會(huì)檢查比如,節(jié)點(diǎn)是否有相同的值?如果是,那么先前的規(guī)則要么不被覆蓋要么以同樣的方式被覆蓋。
節(jié)點(diǎn)的父元素是否指向相同的計(jì)算樣式對象?如果是,那么他們的繼承值將會(huì)相同。
從一開始,這些檢查就處于早期的樣式共享緩存中。但是可能仍然會(huì)有許多樣式不一定匹配的個(gè)例。比如,如果 CSS 規(guī)則使用了 :first-child 選擇器,那么兩個(gè)段落就不一定會(huì)匹配。即使這些檢查建議它們是匹配的。
在 WebKit 和 Blink 中,這些情況會(huì)放棄使用樣式共享緩存。隨著更多的站點(diǎn)使用這些現(xiàn)代選擇器,這種優(yōu)化策略變得越來越不中用了,所以最近 Blink 團(tuán)隊(duì)已經(jīng)移除了這個(gè)功能。結(jié)果卻發(fā)現(xiàn)有另外一種方式來使樣式共享緩存能夠跟上這些改變。
在 Quantum CSS 中,我們將這些怪異的選擇器都集中起來然后檢查它們是否在 DOM 節(jié)點(diǎn)中使用。然后我們將結(jié)果存為 1 和 0。如果兩個(gè)元素有相同的 1 和 0,那么我們就確定了它們是匹配的。
如果一個(gè) DOM 節(jié)點(diǎn)能夠共享已經(jīng)計(jì)算好的樣式,那么你就可以跳過許多的任務(wù)。因?yàn)轫撁嫱ǔ6加泻芏鄻邮较嗤墓?jié)點(diǎn),樣式共享緩存便能夠節(jié)省內(nèi)存并真正地加快運(yùn)行速度。
結(jié)論這是的一個(gè)從 Servo tech 到 Firefox 的重大技術(shù)遷移。一路上,我們學(xué)到了如何將寫在 RUST 中的現(xiàn)代的高性能的代碼帶到 Firefox 的核心中。
我們非常高興能夠?qū)?Quantum 這個(gè)龐大的項(xiàng)目給用戶帶來第一時(shí)間的體驗(yàn)。我們很高興能讓你嘗試使用,如果你 發(fā)現(xiàn)了任何問題請告知我們。
關(guān)于Lin Clark
Lin 是 Mozilla Developer Relations 團(tuán)隊(duì)的一名工程師。 She 專注于 JavaScript, WebAssembly, Rust, 以及 Servo,同時(shí)也繪制一些關(guān)于編碼的漫畫。
code-cartoons.com
@linclark
More articles by Lin Clark…
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/84933.html
摘要:前端日報(bào)精選如何合理地設(shè)計(jì)的深入了解一個(gè)超快的引擎也稱全面了解作用域源碼分析二奇淫技巧總結(jié)整理下前端江湖面試對自己有益的題目。 2017-08-27 前端日報(bào) 精選 如何合理地設(shè)計(jì)Redux的State深入了解一個(gè)超快的 CSS 引擎: Quantum CSS (也稱?Stylo) ★ Mozilla Hacks全面了解JS作用域Zepto源碼分析(二)奇淫技巧總結(jié)整理下《前端江湖面試...
摘要:既然出現(xiàn)了,那怎么辦,目前并沒聽說出現(xiàn)什么新的技術(shù)替代它雖然它真的已經(jīng)很不適合現(xiàn)代的前端了,那么只能開發(fā)一個(gè)新的引擎提高性能,這就是火狐家的量子引擎又叫。這就是所謂的,火狐前一個(gè)引擎所做的那樣。 開始 本文翻譯自Inside a super fast CSS engine: Quantum CSS ,如果想要閱讀原文,可以點(diǎn)擊前往,以下內(nèi)容夾雜本人一些思考,翻譯也并不一定完全。 碎碎念...
摘要:既然出現(xiàn)了,那怎么辦,目前并沒聽說出現(xiàn)什么新的技術(shù)替代它雖然它真的已經(jīng)很不適合現(xiàn)代的前端了,那么只能開發(fā)一個(gè)新的引擎提高性能,這就是火狐家的量子引擎又叫。這就是所謂的,火狐前一個(gè)引擎所做的那樣。 開始 本文翻譯自Inside a super fast CSS engine: Quantum CSS ,如果想要閱讀原文,可以點(diǎn)擊前往,以下內(nèi)容夾雜本人一些思考,翻譯也并不一定完全。 碎碎念...
摘要:既然出現(xiàn)了,那怎么辦,目前并沒聽說出現(xiàn)什么新的技術(shù)替代它雖然它真的已經(jīng)很不適合現(xiàn)代的前端了,那么只能開發(fā)一個(gè)新的引擎提高性能,這就是火狐家的量子引擎又叫。這就是所謂的,火狐前一個(gè)引擎所做的那樣。 開始 本文翻譯自Inside a super fast CSS engine: Quantum CSS ,如果想要閱讀原文,可以點(diǎn)擊前往,以下內(nèi)容夾雜本人一些思考,翻譯也并不一定完全。 碎碎念...
摘要:的主要組件包含了一個(gè)全新的引擎,稱為量子,也稱為。這個(gè)新引擎集成了四種不同瀏覽器的最新創(chuàng)新技術(shù),創(chuàng)造出一個(gè)全新的超級(jí)引擎。這可以發(fā)生在多個(gè)圖層上。最終,擁有最高特異性的規(guī)則會(huì)勝出。 原文:Inside a Super Fast CSS Engine: Quantum CSS(Aka Stylo), Lin Clark 注:原文發(fā)布于 2017 年 8 月,本文翻譯于 2018 年 4 ...
閱讀 2621·2021-09-28 09:35
閱讀 3268·2021-09-03 10:28
閱讀 2920·2019-08-30 15:43
閱讀 1485·2019-08-30 14:04
閱讀 1817·2019-08-29 17:02
閱讀 1821·2019-08-26 13:59
閱讀 702·2019-08-26 11:51
閱讀 3265·2019-08-23 17:16