Day08 X 瀏覽器架構演進史 & 渲染機制
-
第一部分會透過簡易系統架構的角度去看瀏覽器的演進,下列是三個必備的知識點:
- Program 程式:是工程師撰寫的程式碼的集合,例如 Line、 Chrome 就個別是 program。
- 而他們的特點是還沒有被執行,因此也就還沒有被載入至記憶體中,而是存放在次級儲存裝置(例如:硬碟)中。
- Process 程序(對岸用語為進程):指被執行且載入記憶體的 program。
- Process 也是 OS 分配資源的最小單位,可以從 OS 得到如 CPU Time、Memory…等資源,意思是這個 process 在運行時會消耗多少 CPU 與記憶體。
- Thread 執行緒(對岸用語為線程):存在於 process 裡面,而一個進程裡至少會有一個線程。
- 前面有說 process 是 OS 分配資源的最小單位,而 thread 則是作業系統能夠進行運算排程的最小單位,也就是說實際執行任務的並不是進程,而是進程中的線程。
- 一個進程有可能有多個線程,其中多個線程可以共用進程的系統資源。
- 可以把進程比喻為一個工廠,線程則是工廠裡面的工人,負責任務的實際執行。

(還不太了解兩者概念的讀者可以參考關於 Process 與 thread 的文章)
-
第二部分則會介紹瀏覽器的渲染引擎運作機制與渲染流程,是我們想提升網頁效能一個非常重要的切入點。
第一部分:瀏覽器架構演進史
1. Single Process 瀏覽器時期
在 2007 年以前,瀏覽器的所有功能模組都是運行在同一個 Process 裡的。

由上面的架構圖也可以看出不同的功能模組可能運行在不同的 thread 中,然而這種架構衍伸出了幾個明顯的問題。
- 不穩定性:單一程序的瀏覽器中,其中一個功能模組(例如插件)如果出問題壞掉了,會導致整個瀏覽器的崩潰。
- 不流暢性:
- 由於是單一執行緒,意味著同一時刻只有一個功能可以執行。
- 如果遇到一段跑很慢或是無限循環的 JS 程式碼,這段程式碼會獨佔整個 thread 的資源,導致其他模組永遠沒有機會被執行到,這樣的狀況會讓整個瀏覽器失去反應或是變得卡頓。
- 記憶體的空間也是影響瀏覽器效能的重大因素,Single Process 架構的瀏覽器不能完全地回收記憶體,記憶體佔用隨著使用時間越久變得越來越高。
- 安全性問題:沒有實作合理的安全環境(例如 sandbox),因此透過 Plugins 或是 Script 有可能可以獲取系統資源與權限,衍伸出安全性的問題。
2. Multi Processes 瀏覽器時期

-
與 Single Process 的架構差別在於從 Browser Process 中獨立出了兩個 Process:
- Renderer Process 渲染程序:主要負責頁面的渲染
- Process 插件程序:專門運行瀏覽器插件、外掛的程序。
不同 Processes 間需要通過 IPC 來溝通(也就是上圖的虛線)。
IPC:Inter-Process Communication(行程間通訊),指至少兩個行程或執行緒間傳送資料或訊號的一些技術或方法。
-
獨立出 Process 的好處(並解決Single Process 架構的問題):
- ✅ 解決不穩定性的問題:因為各個 Process 是互相隔離的,也就是說如果 Plugin 崩壞,只會影響到它們當前所運行的 Process,並不會導致整個瀏覽器的癱瘓,因此有效解決了不穩定性的問題。
- ✅ 解決不流暢性的問題:瀏覽器中每一個 Tab 都會運行在獨立的 Renderer Process 上。
- 解決「同一時刻只有一個功能可以執行」:即使遇到上面無限循環的 Script,仍會造成頁面失去反應,不過會影響的就只有當前的 Tab 而已,其他的 Tab 因為是運行在不同的 Process,因此仍能正常運作。
- 解決「記憶體佔用」:儘管現在頁面失去反應了,當關閉它時,整個 Renderer Process 也會被關閉,這個 Process 所佔用的記憶體會被系統完整的收回,解決了過往記憶體佔用的問題。
- ✅ 解決不安全性的問題:引用了 「Sandbox 沙盒」的機制,使 Plugin Process 與 Renderer Process 運行在沙盒中。
3. 現代:更加豐富的 Multi Processes 瀏覽器架構
4. 未來世界:SOA 服務導向架構瀏覽器 (Services Oriented Architecture)
一個彈性的解決方案解決高記憶體佔用與架構複雜的問題。

- 將瀏覽器程式的每個部分作為 Services 運行,從而可以輕鬆地拆分為不同的 process 或聚合為一個 process。
Processes 間透過 IPC 來溝通,讓系統架構實現高內聚、低耦合、易擴展與易維護的特性。
- Chrome 會根據設備運用採用不同運行方式:
- ✅ 性能較高的設備:拆成多個 processes 的架構去增強穩定性。
- ❌ 性能較低的設備:採用多個服務合併成單一 Process 的方式來節省記憶體佔用。
第二部分:現今瀏覽器渲染引擎的運作機制
1. 每個 Tab 都會產生一個獨立的 Renderer Process
如何查看:Chrome 瀏覽器的右上角,點擊「更多工具」->「工作管理員」


- 有些頁面顯示為「子頁框」,並且沒有獨立的 Process ID,這是為什麼呢?(見第 3 點)
2. Per-frame Renderer Processes — Site Isolation: 同源, Tab, iframe
Same Origin Policy 同源政策是 Web 裡一個很普遍的安全模型,理論上不同源的網站在未經授權下是要不能存取到彼此的資源的,不然會產生許多安全性問題。
而要做到把兩個不同來源的網站徹底分開,獨立 Process 成為最有效率也最根本的一個方式。
因此在 Chrome 中,實現了每個 Tab 都獨立一個程序的機制。
- 如果在網頁中嵌入不同來源的 iframe,該 iframe 也會運行在不同的 Renderer Process 上。
3. Process Per Site Instance:子頁框, same site
- 預設狀況下:每個 Tab 都會是獨立一個 Renderer Process。
- 在「Same Site」的狀況下:Chrome 預設會將 Same Site的網頁運行在同一個 Process 中。
4. Render Process
-
取得資源 (HTML)
-
主要負責的就是頁面的渲染流程。
- 在瀏覽器輸入 URL 並按下 Enter 後
- 搜尋列會先對輸入做解析:判斷使用者輸入的是 URL 還是搜尋關鍵字
- 並透過 Network Thread 或是 Network Process 去做資源請求
- 並根據回傳的 Content-Type 來決定下一步要交給誰做:如果是回傳的是 HTML,就會準備交由 Renderer Process 進行渲染流程。
-
渲染畫面: Layer, Compositing (rasterize, tile)

5. Reflow & Repaint & Compositing:頁面更新造成的行為、影響頁面效能
Day09 X Resource Hint & Non-Blocking Script Tag
1. Critical Render Path 關鍵渲染路徑
收到 HTML、CSS 和 JavaScript,再對程式碼進行必需的處理,到最後轉變為顯示像素的過程中的步驟。(詳細渲染步驟可以參考 Day 08)
2. Non-Blocking Script:async, defer
-
script 載入流程
當解析 HTML 時遇到 script tag,會立即載入指定的 JavaScript 並在載入後立即執行它,執行完後才會繼續解析 HTML 的工作。

-
async, defer script
3. Resource Hint:對資源預先處理以節省時間
-
目的:對資源預先處理以節省時間。
「由我們提供效能優化指令給瀏覽器」,讓瀏覽器依照我們的指令對不久的將來會用到的資源預先處理。這裡的處理有可能是載入資源、或是建立連線,因此在真的要使用到該資源時可以省去不少時間。
-
優缺點:
- 優點:增加效能
- 缺點:浪費網路資源(預先載入了一堆根本不會用到的資源,對效能只是負擔而已)
-
嚴格的定義:是帶有 rel attribute 的 link tag(5種)
-
preload
:嚴格來說 preload
不算是 Resource Hint,更像是強制性的 command(對瀏覽器而言是 high priority)
- 取得「當前頁面」的重要資源
- 為什麼「preload 不算是 Resource Hint」:
- 因為它有自己獨立的 W3C spec。是一個 deprecated 的 subresource prefetching feature 的替代版本。

-
prefetch
- 不限於當前頁面使用的資源。
- 可以跨越 navigation。
- 例如:你很確定使用者會點擊下一頁,就可以使用 prefetch 預先抓取下一頁的資源,至於什麼時候要下載,則交由瀏覽器自行決定。
-
preconnect
-
<link rel="preconnect" href="https://example.com" />
-
瀏覽器在實際傳輸資源前,會經過這些步驟:
- 向 DNS 請求解析網域名,拿到 IP 地址 (DNS Resolution)
- TCP Handshake
- (HTTPS connection) SSL Negotiation
- 建立連線完成,等待拿到資料的第一個byte
每一個步驟都需要一個 RTT (Round Trip Time) 的來回時間。
利用 preconnect 提早建立好與特定 domain 之間的連線,省去了一次完整的 (DNS Lookup + TCP Handshake + SSL Negotiation) ,共三個 Round Trip Time 的時間。
-
會使用在確定 10 秒 內會用到的 domain:瀏覽器只會保持 preconnect 的 connection 10 秒,超過 10 秒都沒有跟連線目標發送請求,瀏覽器會自動關閉連線。
-
dns-prefetch
- dns-prefetch= DNS look up
- preconnect = DNS look up + TCP Handshake + SSL Negotiation (耗費更多 bandwidth)
- Use Cases
- (👍 best practice) Only For Cross-Origin Domains:因為同源的 IP Address 早就被解析過了。
- preconnect Pair With dns-prefetch
- 因為 preconnet 的瀏覽器支援度比 dns-prefetch 還差,且瀏覽器看到不支援的 hint,會忽略它而不會報錯 (fault-tolerant),在一起使用的狀況下可以確保最小限度先做 DNS Resolution。
-
prerender
- prerender 與 prefetch 都是針對非當前頁面的資源載入。
- prerender 不僅僅會下載對應的資源,還會對資源進行解析。
解析過程中,如果需要其他的資源,還會直接下載或執行這些資源,基本上就是盡可能預先渲染下個頁面。
這樣一來當用戶在從當前頁面跳轉到目標頁面時,瀏覽器可以快速的響應。
- 要非常確定使用者在不久後一定會存取的頁面,不然反而浪費了更多的 Network Bandwidth。
第2~5個 Resource Hint 是真的「Hint」,它們建議瀏覽器可以先去載入哪些資源或是做哪些事,對瀏覽器而言,這些 hint 的 priority 是比較低的,當瀏覽器有 idle time 再去做就好。
-
瀏覽器支援度:
4. 本日小結:使用 JavaScript 動態產生這些 Resource Hint 以便維護
- 使用 Resource Hint 也很容易讓 code 變得難以維護
- 如果都單純把 hint 加到 HTML 裡,萬一資源有變動,要改動的話十分麻煩
- 比較合理的方式可能是使用 JavaScript 動態產生這些 Resource Hint,可以把相關的 hint 寫在同一個 file 維護上也變得更加容易
- Resource Hint 都是 body-ok 的 link,要放在 HTML 的 body 也是可以 work 的
References
今晚,我想來點 Web 前端效能優化大補帖!