【三】 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需要進一步在這個地方琢磨點方法的。。。