Chrome 渲染優化 - 層模型

發表於2013-07-10

引言

對於大多數Web開發者而言,Web頁面的基本模型就是DOM模型。頁面的渲染過程常常並不為人所知,大家只是知道它是一個將頁面的DOM表示方式轉化為在螢幕上顯示的一個圖片的過程。近幾年,現代瀏覽器利用圖形卡對頁面渲染方式進行了改進:這種改進一般都籠統地稱為“硬體加速”。當談及普通Web頁面(也即:非Canvas2D或者WebGL頁面)時,硬體加速這個詞到底意味著什麼?本文將對作為Chrome中硬體加速下Web頁面渲染基礎的基本模型進行說明。

冗長的注意事項

我們在這裡討論的是WebKit,更具體地講,我們討論的是Chromium下的WebKit。 本文所涉及的是Chrome的實現細節,而不是Web平臺的特性。Web平臺及其標準並沒有對這個層次的實現細節做出詳細的規定,所以我們並不能保證本文中的內容可以適用於其它的瀏覽器,但是對瀏覽器內部工作機理的瞭解定會有益於對應用進行高水平的錯誤除錯和效能調優。

另外還要主意,整個這篇文章討論的都是Chrome渲染架構中的一個核心部分,但這個結構仍在迅速變化之中。本文試圖僅僅涉及那些不太可能發生變化的部分,但要是在6個月後再看這篇文章,那可就不一定是什麼情況了。

很重要的是要明白Chrome當下有段時間還會有兩個不同的渲染途徑:硬體加速式的渲染和早期軟體式的渲染。直到寫這篇文章之時為止,在Windows、ChromeOS和Android下的Chrome中,所有的頁面都走的是硬體加速途徑。在Mac和Linux下, 只有那些部分內容需要組合的頁面才會走硬體加速途徑(更多關於什麼才需要組合的說明請參見下文),但用不了多久,所有頁面將都會走硬體加速途徑。

最後要說的是,我們在這裡的內容只是對Chrome的渲染引擎的管窺之見,所看到的只是其對效能影響比較大的一些特性。當你想要提高你的網站的效能時,對層模型有所瞭解會非常有幫助作用,但這也容易造成搬起石頭砸自己腳的情況:層這種結構非常有用,但是建立過多的層會造成整個圖形棧開銷的增加。到時候可別說我沒有提前警告過你哦!

從DOM到螢幕

層的引入

頁面一旦在裝入並解析完成後,就會表示為許多Web開發者所熟悉的結構:DOM。然而,在頁面的渲染過程中,瀏覽器還具有一系列並不直接暴露給開發者的頁面中間表示方式。這些表示方式中最重要的結構就是層。

在Chrome中實際上有幾種不同型別的層:掌管DOM子樹的渲染層(RenderLayer)以及掌管渲染層子樹的圖形層(GraphicsLayer)。 我們這裡最關心的是後者,因為作為紋理上傳到GPU之中的就是圖形層。本文自此就只用“層”來指代圖形層了。

稍稍離題說一下同GPU有關的幾個術語:什麼是紋理?紋理可以看作是點陣圖影象,從主儲存器(也就是RAM)傳遞到視訊儲存器(也就是GPU之上的VRAM)之中的就是這個影象。一旦傳遞到GPU之中後,你就能夠將紋理對映到一個網格幾何結構之上 —— 在視訊遊戲或者CAD程式中,這種技術用來給框架式的3D模型新增“皮膚”。Chrome採用紋理把頁面中的內容分塊傳送給GPU。紋理能夠以很低的代價對映到不同的位置,而且還能夠以很低的代價通過把它們應用到一個非常簡單的矩形網格中進行變形。這就是3D CSS的實現原理,而且這麼做對頁面在螢幕的快速滾動也非常好 —— 現在先說這些,這兩方面更詳細的探討情況下文。

下面讓我們用幾個例子來說明層的概念。

在Chome中研究層時有一個非常有用的工具就是Chrome的開發者工具裡設定(也就是那個小齒輪圖示)中“渲染(rendering)”小標題下的“顯示層的組合邊界(show composited layer borders)”開關。讓我們把這個開關開啟。本文中所有的截圖和例子都來自最新版的Chrome Canary,在寫這篇文章的時候是Chrome 27。

圖1: 只有一層的頁面 (將在新視窗中開啟)

(譯者注:這裡缺了一個圖,原文中的圖就看不到,可能是需要梯子?)

在頁面的基本層中組合層的渲染邊界螢幕截圖

這個頁面只有一層。藍色的柵格表示的是分塊,這些分塊可以看作是比層更低一級的單位,Chrome以這些分塊為單位,一次向GPU上傳一個分塊的內容。這裡它們並不怎麼重要。

圖2:有自己的層的元素 (open stand-alone)

(譯者注:這裡缺了一個圖,原文中的圖就看不到,可能是需要梯子?)

旋轉後層的渲染邊界的截圖

在<div>上加上讓它旋轉一個角度的3D CSS屬性後,我們就能夠看到一個元素有自己的層是個什麼樣子:請注意其中的橘色邊界,這個邊界給出了這個檢視中層的輪廓。

層的建立準則

還要其它什麼元素會得到自己的層?Chrome在這方面採用的規則仍在隨著時間推移逐漸發展變化,但在目前下面這些因素都會引起Chrome建立層:

  • 進行3D或者透視變換的CSS屬性
  • 使用硬體加速視訊解碼的<video>元素
  • 具有3D(WebGL)上下文或者硬體加速的2D上下文的<canvas>元素
  • 組合型外掛(即Flash)
  • 具有有CSS透明度動畫或者使用動畫式Webkit變換的元素
  • 具有硬體加速的CSS濾鏡的元素
  • 子元素中存在具有組合層的元素的元素(換句話說,就是存在具有自己的層的子元素的元素)
  • 同級元素中有Z索引比其小的元素,而且該Z索引比較小的元素具有組合層(換句話說就是在組合層之上進行渲染的元素)

實際意義:動畫

我們還可以將層在頁面中到處移動,正好可用於動畫。

圖3: 動畫層(將在新視窗中開啟

正如前文所述,層在將Web頁面中的靜態內容隨處移動方面真的很有用。在最基本的情況下,Chrome將層中的內容繪製到軟體點陣圖中,然後再將該點陣圖作為紋理上載到GPU之中。如果層中的內容將來不會發生變化,那就不需要對其進行重繪了。這是個好事:重繪將會佔用本可以用於幹其它事情,比如執行JavaScript的時間,而且如果繪製過程太長,動畫還會出現卡頓現象。

例如,可以在Chrome的開發工具中看一下這個頁面的時間線:但該層在來回旋轉的時候,並沒有出現繪製操作。

動畫過程中的開發者工具時間線螢幕截圖

無效!重繪

但是如果層中的內容發生了改變,它就需要重繪了。

圖4:層的重繪 (將在新視窗開啟)

每次點選按鈕後,旋轉中的元素的寬度就會增加1px。這將導致頁面重新佈局,整個元素都需要重繪,在這個例子中需要重繪的是整個層。

檢視Chrome重繪了哪些元素的一個很好的辦法是使用開發者工具中的“顯示繪製矩形”開關,這個開關同樣也在開發者工具設定中的“渲染”標題下。開啟該開關後,在點選按鈕的時候請注意動畫中的元素和按鈕周圍都會有一個紅色的矩形閃現。

Chrome 渲染優化 - 層模型

顯示繪製矩形檢查框的螢幕截圖

同時繪製時間也出現在開發者工具裡的時間線中了。明眼的讀者可能還注意到這裡有兩個繪製事件:一個是層的,另外一個是按鈕的。按鈕會在它的狀態變成按下狀態和從按下狀態變為非按下狀態時需要進行重繪。

Chrome 渲染優化 - 層模型

開發者工具時間線中層的重繪的螢幕截圖

請注意Chrome並不總是需要重繪整個層,它會盡量地以聰明的方式只繪製DOM中發生變化的那部分內容。在我們的這個例子中,我們所改變的DOM元素是整個層的尺寸。但是在很多情況下,在一層中會有大量的DOM元素。

很顯然接下來的問題是頁面內容失效和強制進行重繪是由什麼引起的。要全面回答這個問題並不容易,因為引起強制進行重繪的有大量不太容易區分的情況。其中最常見的原因是通過操縱CSS樣式或者引起重新進行頁面佈局來改變DOM的特性。Tony Gentilcore寫了一篇很不錯的討論引起頁面重新佈局的原因的博文,Stoyan Stefanov有一篇更近詳盡地討論瀏覽器繪製過程的文章(但這篇文章僅僅止於繪製過程,並沒有涉及奇特的組合部分的內容)。

找出重繪是否影響到了某些你正在關注中的元素的最好方式是使用開發者工具中的時間線以及“顯示繪製矩形”工具,用這兩個工具能夠看出瀏覽器是否在重繪一些你並不認為需要重繪的內容,然後找出就在重新佈局/重繪前的那個時刻之前到底是在哪裡改變了DOM結構。如果繪製過程無法避免但繪製過程卻長的不太合理,請參考一下Eberhard Gräther的文章,這是一篇關於在開發者工具中如果對持續性繪製模式進行效能優化的文章。

總結:從DOM到螢幕

Chrome是如何將DOM轉換為螢幕上的影象的呢?從概念上講,它:

  1. 取得DOM並將其分成若干個層
  2. 分佈將這些層繪製到各自的軟體點陣圖中
  3. 將繪製好的點陣圖作為紋理上載至GPU之中
  4. 將這些不同的層組合起來形成螢幕上最後顯示出的影象

這些步驟在Chrome第一次產生Web頁面的幀時都需要執行。但是在產生隨後的幀時,就可能會走一些捷徑:

  • 如果有某些CSS屬性發生了改變,就並不一定要重繪所有的內容了。Chrome能夠將已經作為紋理儲存在GPU之中的層重新組合起來,但只是在重新組合時,使用不同的組合屬性(比如,在不同的位置、以不同的透明度來組合等等)。
  • 如果某一層中的某個部分的內容變成無效的了,那麼該層就需要重繪並在重繪完成後重新上載至GPU中。如果其內容仍然不變,只是其組合屬性發生了變化(比如它的位置或者透明度改變了),Chrome就不會對GPU中該層的點陣圖做任何處理,只是通過重新進行組合來生成新的幀。

現在大家應該弄清楚了,基於層的組合模型對渲染效能有著非常大的意義。在沒有需要重新繪製的內容時,組合的代價相對來說代價更低一些,所以在除錯渲染效能時,避免層的重繪是一個非常好的總體目標。經驗老道的開發者會去看上文提到的組合觸發的列表,並意識到很可能會非常容易的導致瀏覽器建立很多層。但是,一定要小心無意識地去建立層,因為使用層是有代價的:它們會佔用系統RAM以及GPU中的儲存空間(移動裝置上的儲存空間特別有限),層太多的話還會在跟蹤哪些從可見哪些層不可見的演算法中引入額外的開銷。如果層都很大而且原先不重疊的層突然重疊了起來,那麼太多的層就會增加瀏覽器花在柵格化方面的時間,這會導致有時稱之為“過度繪製”的情況。所以,一定要明智地運用你所學到的知識!

暫時先寫到這裡了。請你以後再到這裡來檢視另外幾篇關於層模型實際意義的文章。

其它參考資料

  1. Scrolling Performance
  2. Profiling Long Paint Times with DevTools’ Continuous Painting Mode
  3. http://jankfree.com
  4. http://aerotwist.com/blog/on-translate3d-and-layer-creation-hacks/

相關文章