Chrome源碼剖析【三】Chrome的進程模型

1. 基本的進程結構

Chrome是一個多進程的架構,不過所有的進程都會由老大,Browser進程來管理,走的是集中化管理的路子。在Browser進程中,有xxxProcessHost,每一個host,都對應着一個Process,比如RenderProcessHost對應着RenderProcess,PluginProcessHost對應着PluginProcess,有多少個host的實例,就有多少個進程在運行。。。
這是一個比較典型的代理模式,Browser對Host的操作,都會被Host封裝成IPC消息,傳遞給對應的Process來處理,對於大部分上層的類,也就隔離了多進程細節。。。

2. Render進程

先不扯Plugin的進程,只考慮Render進程。前面說了,一個Process一個tab,只是廣告用語,實際上,每一個web頁面內容(包括在tab中的和在彈出窗口中的...),在Chrome中,用RenderView表示一個web頁面,每一個RenderView可以寄宿在任一一個RenderProcess中,它只是依託RenderProcess幫助它進行通信。每一個RenderProcess進程都可以有1到N個RenderView實例。。。
Chrome支持不同的進程模型,可以一個tab一個進程,一個site instance一個進程等等。但基本模式都是一致的,當需要創建一個新的RenderView的時候,Chrome會嘗試進行選擇或者是創建進程。比如,在one site one process的模式下,如果存在此site,就會選擇一個已有的RenderProcessHost,讓它管理這個新的RenderView,否則,會創建一個RenderProcessHost(同時也就創建了一個Process),把RenderView交給它。。。
在默認的one site instance one process的模式中,Chrome會爲每個新的site instance創建一個進程(從一個頁面鏈開來的頁面,屬於同一個site instance),但,Render進程總數是有個上限的。這個上限,根據內存大小的不同而異,比如,在我的機器上(2G內存),最多可以容納20個Render進程,當達到這個上限後,你再開新的網站,Chrome會隨機爲你選擇一個已有的進程,把這個網站對應的RenderView給扔進去。。。
每一次你新輸入一個站點信息,在默認模式下,都必然導致一個進程的誕生,很可能,伴隨着另一個進程的死亡(如果這個進程沒有其他承載的RenderView的話,他就自然死亡了,RenderView的個數,就相當於這個進程的引用計數...)。比如,你打開一個新標籤頁的時候,系統爲你創造了一個進程來承載這個新標籤頁,你輸入www.baidu.com,於是新標籤頁進程死亡,承載www.baidu.com的進程誕生。你用baidu搜索了一下,毫無疑問,你基本對它的搜索結果很失望,於是你重新輸入www.google.com,老的承載baidu的進程死亡,承載google的進程被構建出來。這時候你想回退到之前baidu的搜索結果,樂呵樂呵的話,一個新的承載baidu的進程被創造,之前Google的進程死亡。同樣,你再次點擊前進,又來到Google搜索結果的時候,一個新的進程有取代老的進程出現了。。。
以上現象,你都可以自己來檢驗,通過觀察about:memory頁面的信息,你可以瞭解整個過程(記得每做一步,需要刷新一下about:memory頁面)。我唧唧歪歪說了半天,其實想表達的是,Chrome並沒有像我YY的一樣做啥進程池之類的特殊機制,而是簡單的履行有就創建、沒有就銷燬的策略。我並不知道有沒有啥很有效的多進程模型,這方面一點都沒玩過,猜測Chrome之所以採取這樣的策略,是經過琢磨的,覺得進程生死的代價可以承受,比較可行。。。

3. 進程開銷控制算法

說開銷無外乎兩方面的內容,一爲時間,二則空間。Chrome沒有在進程創建和銷燬上做功夫,但是當進程運行起來後,還是做了一些工作的。。。
節約工作首先從CPU耗時上做起,優先級越高的進程中的線程,越容易被調度,從而耗費CPU時間,於是,當一個頁面不再直接面對用戶的時候,Chrome會將它的進程優先級切到Below Normal的級別,反之,則切回Normal級別。通過這個步驟,小節約了一把時間。。。

進程的優先級
在windows中,進程是有優先級的,當然,這個優先級不是真實的調度優先級,而是該進程中,線程優先級計算的基準。在《Windows via C/C++》(也就是《windows核心編程》的第五版)中,有一張詳細的表,表述了線程優先級和進程優先級的具體對應關係,感覺設計的很不錯,我就不罰抄了,有興趣的自行動手翻書。。。

當然這只是一道開胃小菜,滿漢全席是控制進程的工作集大小,以達到降低進程實際內存消耗的目的(Chrome爲了體現它對內存的節約,用了“更爲精確”的內存消耗計算方法...)。提到這一點,Chrome頗爲自豪,在文檔中,順着道把單進程的模式鄙視了一下,基本意思是:在多進程的模式下,各個頁面實際佔用的內存數量,更容易被控制,而在單進程的模式下,幾乎是不能作出控制的,所以,很多時候,多進程模式耗費的內存,是會小於多線程模式的。這個說法靠不靠譜,大家心裏都有譜,就不多說了。。。
具體說來,Chrome對進程工作集的控制算法還是比較簡單的。首先,在進程啓動的時候,需要指明進程工作的內存環境,是高內存,低內存,還是中等內存,默認模式下,是中等內存(我以爲Chrome會動態計算的,沒想到竟然是啓動時指定...)。在高內存模式,不存在對工作集的調整,使勁用就完事了;在低內存的模式下,調整也很簡單,一旦一個進程不再有頁面面對觀衆了,嘗試釋放其所有工作集。相比來說,中等模式下,算法相對複雜一些,當一個進程從直接面對觀衆,淪落到切換到後臺的悲慘命運,其工作集會縮減,算法爲: TargetWorkingSetSize = (LastWorkingSet/2 + CurrentWorkingSet) /2;其中,TargetWorkingSetSize指的是預期降到的工作集大小,CurrentWorkingSet指的是進程當前的工作集(在Chrome中,工作集的大小,包含私有的和可共享的兩部分內存,而不包含已經共享了的內存空間...),LastWorkingSet,等於上一次的CurrentWorkingSet除以DampingFactor,默認的DampingFactor爲2。而反之,當一個進程從幕後走向臺前,它的工作集會被放大爲 LastWorkingSet * DampingFactor * 2,瞭解過LastWorkingSet的含義,你已經知道,這就是將工作集放大兩倍的另類版寫法。。。
Chrome的Render進程工作集調整,除了發生在tab切換(或新頁面建立)的時候,還會發生在整個Chrome的idle事件觸發後。Chrome有個計時器,統計Chrome空閒的時長,當時長超過30s後(此工作會反覆進行...),Chrome會做一系列工作,其中就包括,調整進程的工作集。被調整的進程,不僅僅是Render進程,還包括Plugin進程和Browser進程,換句話描述,就是所有Chrome進程。。。
這個算法導致一個很悲涼的狀況,當你去蹲了個廁所回到電腦前,切換了一個Chrome頁面,你發現頁面一片慘白,一陣硬盤的騷動過後,好不容易恢復了原貌。如果再切,相同的事情又會發生,孜孜不倦,直到你切過每一個進程。這個慘案發生的主要原因,就是由於所有Chrome進程的工作集都被釋放了,頁面的重載和Render需要不少的一坨時間,這就大大影響了用戶感受,畢竟,總看到慘白的畫面,容易產生不好的情緒。強烈感覺這個不算一個很出色的策略,應該有一個工作集切換的底限,或者是在Chrome從idle中被激活的時候,偷偷摸摸的統一擴大工作集,發幾個事件刺激一下,把該加載的東西加載起來。。。
整體感覺,Chrome對進程開銷的控制,並不像想象中的有非常精妙絕倫的策略在裏面,通過工作集這總手段並不算華麗,而且,如果想很好的工作的話,有一個非常非常重要的前提,就是被切換的頁面,很少再被繼續瀏覽。個人覺得這個假設並不是十分可靠,這就使得在某些情況下,產生非常不好的用戶體驗,也許Chrome需要進一步在這個地方琢磨點方法的。。。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章