圖解瀏覽器的多程式渲染機制

蘭俊秋雨發表於2023-01-11

引言

觀察瀏覽器的工作管理員可以發現,開啟瀏覽器的一個頁面需要多個程式,包括瀏覽器程式、GPU 程式、網路程式、渲染程式等,有外掛的話還會包括各種外掛程式(Chrome 選項 -> 更多工具 -> 工作管理員)。

圖片

本文將聚焦於瀏覽器的各個程式間是如何配合,將頁面呈現給使用者的。

? 你將瞭解到

  • 瀏覽器在歷史發展過程中,其程式架構做了哪些調整,為什麼這樣調整,以及解決了哪些問題?
  • 從使用者在位址列輸入 URL,到頁面渲染完成這之間發生了什麼?迴流和重繪是如何對瀏覽器效能造成影響的?

圖片

1. 瀏覽器程式架構的演化

程式和執行緒

圖片

程式

  • 一個程式就是一個程式的執行例項,它是由用來存放程式碼、執行中的資料以及一個執行任務的主執行緒的記憶體組成的執行環境;
  • 當一個程式關閉後,作業系統會回收為該程式分配的記憶體(即使該程式中存在因操作不當導致記憶體洩漏的執行緒);
  • 程式之間的內容是相互隔離的,這是為了保護作業系統中的程式互不干擾;
  • 當程式之間需要進行通訊時,可使用程式間通訊(IPC)機制。

執行緒

  • 執行緒是由程式來啟動和管理的,一個應用程式在執行的時候會存在多個子任務的情況,使用多執行緒並行處理可以大大提升效能;
  • 由於執行緒依附於程式,程式中的任一執行緒執行出錯也會導致整個程式的崩潰(因為記憶體是共享的);
  • 同一程式中的多個執行緒可共享程式所擁有的資源。這種資源包括記憶體空間,也包括作業系統的許可權。
單程式和多程式瀏覽器

單程式瀏覽器

單程式瀏覽器是指所有功能模組(網路、外掛、JS 執行環境、渲染引擎、頁面等)都執行在同一程式中的瀏覽器(早期的 IE、Firefox)。

圖片

單程式瀏覽器存在的問題:

  • 【不穩定】
    • 瀏覽器中的外掛執行在瀏覽器的程式之中,外掛的崩潰會引起整個瀏覽器的崩潰;
    • 渲染引擎通常也是不穩定的,例如複雜的 JS 指令碼也會引起渲染引擎的崩潰,最終導致瀏覽器崩潰。
  • 【不流暢】
    • CPU 在某個時間點只能執行某個程式中的某一條執行緒。由於單程式瀏覽器中所有的頁面的各種模組都在同一執行緒中執行,即同一時刻只能有一個模組可以執行。
    • 當一個頁面的某個模組阻塞了該執行緒,就會導致整個瀏覽器失去響應;此外,頁面的記憶體洩漏也會導致單程式瀏覽器使用時間越長,反應越慢。
  • 【不安全】
    • 執行緒共享程式資源,因而外掛就能獲取到瀏覽器執行過程中的資料,以及擁有和瀏覽器同等的系統許可權。
    • 例如,外掛可使用 C/C++ 編寫,透過外掛可以獲取到作業系統任意資源;指令碼也可以透過瀏覽器的漏洞來獲取系統許可權,引發安全問題。
多程式瀏覽器

Chrome 一問世便使用了多程式的架構,其頁面執行在了單獨的渲染程式中,外掛執行在單獨的外掛進行中,程式間使用 IPC 進行通訊。

瀏覽器的主要程式有哪些:

圖片

  • 瀏覽器程式。相當於瀏覽器的大腦,主要負責介面顯示、使用者互動、子程式管理,同時提供儲存等功能。
  • 渲染程式。核心任務是將 HTML、CSS 和 JavaScript 轉換為使用者可以與之互動的網頁,排版引擎 Blink 和 JavaScript 引擎 V8 都是執行在該程式中。
預設情況下,Chrome 會為每個 Tab 標籤建立一個渲染程式。因為渲染程式所有的內容都是透過網路獲取的,會存在一些惡意程式碼利用瀏覽器漏洞對系統進行攻擊,所以執行在渲染程式裡面的程式碼是不被信任的。這也是為什麼 Chrome 會讓渲染程式執行在安全沙箱裡,就是為了保證系統的安全。
  • 網路程式。主要負責頁面的網路資源載入,之前是作為一個模組執行在瀏覽器程式裡面的,目前已獨立出來,成為一個單獨的程式。
  • 外掛程式。主要是負責外掛的執行,因外掛易崩潰,所以需要透過外掛程式來隔離,以保證外掛程式崩潰不會對瀏覽器和頁面造成影響。
  • GPU 程式。當頁面使用了硬體加速時,會使用它來渲染頁面。
其實,Chrome 剛開始釋出的時候是沒有單獨 GPU 程式的,都是放到瀏覽器主程式中的。而 GPU 的使用初衷是為了實現 3D CSS 的效果,只是隨後網頁、Chrome 的 UI 介面都選擇採用 GPU 來繪製,這使得 GPU 成為瀏覽器普遍的需求。最後,Chrome 在其多程式架構上也引入了 GPU 程式。

多程式瀏覽器是如何解決單程式瀏覽的問題的:

  • 【不穩定】正是由於程式之間相互隔離,當一個頁面或者外掛崩潰時只會影響當前的程式,不會影響到瀏覽器和其他頁面。
  • 【不流暢】由於 JS 指令碼執行在渲染程式中,即使 JS 阻塞了渲染程式,也只會影響當前頁面的渲染,而其他頁面的指令碼則會執行在他們自己的渲染程式中,不受影響;此外,記憶體洩漏導致的不流暢問題也會隨著一個頁面的關閉導致一個程式的結束而解決。
  • 【不安全】多程式架構的安全沙箱,相當於是作業系統給程式上了一把鎖,沙箱中的程式可執行不可寫入、不可讀取敏感資料。

多程式瀏覽器存在的問題:

  • 更高的資源佔用。以 Chrome 瀏覽器為例,其將為每個頁面分配單獨的渲染程式,為每個外掛分配單獨的外掛程式,因此會消耗更多記憶體資源。
  • 更復雜的體系架構。瀏覽器各個模組之間耦合度高、擴充套件性差目前的架構較難適應新需。

2. 導航流程

從使用者發出 URL 請求到頁面開始解析的過程,叫做導航,是網路載入流程和渲染流程之間的橋樑。

圖片

  • 首先,瀏覽器程式接收到使用者輸入的 URL 請求,瀏覽器程式便將該 URL 透過 IPC 轉發給網路程式。
  • 然後,在網路程式中發起真正的 URL 請求。
  • 接著網路程式接收到了響應頭資料,便解析響應頭資料,並將資料轉發給瀏覽器程式。
  • 瀏覽器程式接收到網路程式的響應頭資料之後,傳送 “提交文件 (CommitNavigation)” 訊息到渲染程式。
  • 渲染程式接收到 “提交文件” 的訊息之後,便開始準備接收 HTML 資料,接收資料的方式是直接和網路程式建立資料管道。
  • 待網路程式中文件資料傳輸完成,渲染程式會向瀏覽器程式 “確認提交”,這是告訴瀏覽器程式:“已經準備好接收和解析頁面資料了”。
  • 瀏覽器程式接收到渲染程式 “確認提交” 的訊息之後,導航流程就結束了。此時,渲染程式就會開始解析頁面和載入子資源了,瀏覽器程式將開始移除之前舊的文件,然後更新瀏覽器程式中的頁面狀態。

3. 渲染流程

渲染流水線

渲染流水線可分為如下幾個子階段:構建 DOM 樹、樣式計算、佈局、分層、繪製、分塊、光柵化和合成。

圖片

構建 DOM 樹(DOM)

瀏覽器無法直接理解和使用 HTML,所以要將其轉化為瀏覽器能夠理解的解構 —— 經過 HTML 解析器解析,輸出樹狀結構的 DOM

圖片

樣式計算(Style)

目的是計算 DOM 節點中的每個元素具體樣式,可分為三步

  • 渲染引擎把 CSS 文字轉為瀏覽器可理解的結構 ——styleSheets 樣式表
  • 標準化樣式表中的屬性值。這是由於渲染引擎無法理解 CSS 文字中的各種屬性值,這些值會被轉為標準化的計算值(例如 {color: blue} → {color: rgb(0, 0, 225)}、{font-weight: bold} → {font-weight: 700}
  • 計算出 DOM 樹中每個節點的具體樣式,計算過程遵守 CSS 的繼承和層疊規則,被儲存在 ComputedStyle 結構內

佈局階段(Layout)

計算 DOM 樹中可見元素的幾何位置資訊,包括建立佈局樹和佈局計算兩個階段

  • 建立佈局樹
    • 遍歷 DOM 樹中的所有需要渲染節點,並新增到佈局樹中
    • 不可見的節點如 head 標籤下的全部內容,display: none 的標籤等會被忽略

圖片

  • 佈局計算
    • 計算 DOM 節點的位置座標,佈局運算的結果會被寫回佈局樹中

分層(Layer)

針對頁面中的複雜效果,例如複雜的 3D 變換、頁面滾動、z 軸排序等,渲染引擎將為特定節點生成專用的圖層,並生成一顆圖層樹(Layer Tree)

圖片

擁有層疊上下文屬性的元素會被提升為單獨的一層;需要剪裁的地方也會被建立為單獨的圖層

注意,並非佈局樹的每個節點都包含一個圖層,一個節點可以直接或間接地屬於一個層,例如一個節點可以從屬於父節點的圖層

圖片

圖層繪製(Paint)

渲染引擎會對圖層樹中每個圖層進行繪製,將一個圖層的繪製拆分成很多小的繪製指令,然後把這些指令按順序組成一個待繪製列表

圖片

柵格化(生成點陣圖)

繪製列表準備好後,主執行緒將其提交給合成執行緒,實際的繪製操作由渲染引擎中的合成執行緒來完成

  • 合成執行緒會根據視口位置和大小,將圖層(layer)劃分為塊(圖塊 tile)
  • 合成執行緒會按照視口附近的圖塊來優先生成點陣圖,實際生成點陣圖的操作由柵格化(將圖塊轉換為點陣圖)來執行,圖塊是柵格化的最小單位
  • 渲染程式會維護一個柵格化的執行緒池,柵格化過程通常都會使用 GPU 來加速生成,使用 GPU 生成點陣圖的過程叫做快速柵格化,生成的點陣圖被儲存在 GPU 記憶體中

圖片

合成與顯示

  • 所有圖塊都被柵格化後,合成執行緒將生成繪製圖塊命令 DrawQuad 提交給瀏覽器程式
  • 瀏覽器程式中 viz 元件接收 DrawQuad 命令,根據此命令,將其頁面內容繪製在記憶體中,最後再顯示到螢幕上
流水線總結
  • 渲染程式將 HTML 內容轉換為能夠讀懂的 DOM 樹結構。
  • 渲染引擎將 CSS 樣式錶轉化為瀏覽器可以理解的 styleSheets,計算出 DOM 節點的樣式。
  • 建立佈局樹,並計算元素的佈局資訊。
  • 對佈局樹進行分層,並生成分層樹。
  • 為每個圖層生成繪製列表,並將其提交到合成執行緒。
  • 合成執行緒將圖層分成圖塊,並在光柵化執行緒池中將圖塊轉換成點陣圖。
  • 合成執行緒傳送繪製圖塊命令 DrawQuad 給瀏覽器程式。
  • 瀏覽器程式根據 DrawQuad 訊息生成頁面,並顯示到顯示器上。

圖片

迴流和重繪

基於上述瀏覽器的渲染原理,我們可以理解迴流和重繪是如何對瀏覽器效能造成影響的。由於瀏覽器渲染頁面預設使用流式佈局模型,當某個 DOM 或 CSS 幾何屬性發生改變後,文件流就會受到波動,就需要對 DOM 重新進行計算,重新佈局頁面,引發迴流。

更新元素幾何屬性 —— 迴流

  • 幾何屬性的修改會觸發瀏覽器重新佈局(Layout & Layer),渲染樹需要重新生成,解析後來的一系列子階段
  • 因此迴流需要更新完整的渲染流水線,開銷是最大的

圖片

更新元素繪製屬性 —— 重繪

  • 繪製屬性的修改並沒有導致幾何位置的變化,所以不會導致佈局階段的執行,會直接進入繪製階段,然後執行後來的子階段
  • 重繪操作相比迴流省去了佈局和分層階段,效率高於迴流

圖片

GPU 加速 —— 直接合成

  • 如果更改的屬性不需要佈局和繪製,渲染引擎會跳過佈局和繪製,直接進入非主執行緒 —— 合成執行緒執行後續合成操作(比如利用 CSS3 的 transform、opacity、filter 這些屬性就可以實現合成效果)
  • 例如,使用 CSS transform 實現動畫效果的渲染流水線如下:一是避開了重繪、迴流,因此避開了佈局和繪製階段;二是直接在非主執行緒執行合成動畫操作,未佔用主執行緒資源。相比於重繪和迴流,合成大大提升了繪製效率

圖片

Reference

  • 瀏覽器工作原理與實踐
  • 瀏覽器程式架構的演化&version=13020110&nettype=WIFI&lang=zh_CN&fontScale=100&exportkey=n_ChQIAhIQGRRXFZ2dXQmFA2Bx5wQ4oBKAAgIE97dBBAEAAAAAAOFZNG6p7%2BUAAAAOpnltbLcz9gKNyK89dVj0RmL9G6zZTdJ1mZshRcBg33o4bJEPjfSX4iFReWCjilqk0NfpL0h1iqYKkX2DdW6yD0Adc4JldMIlENpdopQIarHLIcAACAIRLaSzIqVEbSy5JLdxC4wsdBcQ45Ql0oUR6jPwx7%2FIvQ40tphDDC3%2ByysOom29zfOo99XihFQ13nDQARASxLec1XBIK4vt6hKJwK1DVvZvNjCymwWdBeXRAG1acroj2axfwR4dzK161v9LTf9%2BMWeBD%2Bh%2FvEGKWvIRLLB0n8twV5OzxPdRqza4JjY5KY0kXWIpR7o%3D&acctmode=0&pass_ticket=NROenW8KktFdpx%2FehWWP2BcPvnem3EVXgzUh1htgRSOooPPCrE4swwsoZDu1C8jdABIA6JxLm7%2Ffo6AQWCBOmQ%3D%3D&wx_header=0&fontgear=2.000000)

關於本文
作者:@高揚
原文:https://mp.weixin.qq.com/s/6Q...

相關文章