Sapphire算法:GC Without Stop the World(上)

Go的GC一致爲人詬病,然而Go1.5據說大大優化了GC,具體可以見這篇文章http://www.oschina.net/translate/go-gc-solving-the-latency-problem-in-go-1-5

於是我打開了Go源代碼,查看了Go GC相關代碼,註釋中說,Go現在使用的GC是一種不用停止世界的GC,基於Richard大師2001年的論文,我便翻譯了這篇paper,試圖去理解這種GC算法,論文比較長,準備分爲三篇發佈,以下是正文:


Sapphire: Copying GC Without Stopping the World

Richard L.Hudson Intel Corporation 2200 Mision College Blvd. Santa Clara, CA 95052-8119 [email protected]

J. Eliot B. Moss Dept. Of Computer Science Univ. Of Massachusetts Amherst, MA 01003-4610 [email protected]

翻譯:InsZVA

摘要

很多同時進行的垃圾回收(GC)算法已經被設計出來,然而很少有被實行和評估的,尤其是爲了Java程序設計語言的。我們設計的Sapphire是一種同時拷貝GC算法。Sapphire注重最小化任何給定的線程需要阻塞以支持控制器的時間數量。(譯者注:像Go1.5以前那種長時間GC暫停,甚至可達300ms,嚴重影響服務程序)特別地,Sapphire致力於工作在存在大量線程運行於小到中規模共享內存的多核處理器的情況下。一個Sapphire要處理的具體問題是在線程棧被調整來複制對象時,不能停止所有的線程(用GC用語講,新對象的flip)。

Sapphire拓展了先前的算法,而且是與複製回收(copying collection)最近的一種GC技術,應用線程主要observe對象舊的拷貝【13】。Sapphire的最關鍵創新在於:(1)在同一時刻flip一個線程的能力(也就是將線程中對對象的舊副本的引用換成對對象新副本的引用),而不是在同一時刻停止所有線程來flip他們;(2)避免使用讀屏障。

1.概述

Sapphire是一種爲類型安全、堆分配內存的語言設計的新的同時複製GC算法。它致力於最小化垃圾回收時應用線程阻塞的時間。Sapphire修改優先算法的一個進步是對線程的flip的提升。之前的算法包括了一個使得所有線程全部停止的步驟,這個步驟中,線程的棧改變了,而且棧中的指針從對象的舊副本重定義到新副本。在可能含有多個線程的系統裏,我們預測這種暫停可能是不可接受的(譯者注:預見了Go嗎,233~然後你就被Go挖去做GC了)。Sapphire也避免了讀屏障的使用。

Sapphire目標程序是使用多核處理器的服務器程序。這種程序可能會擁有大量的線程,每個線程處理一個網絡會話(譯者注:簡直就是爲Go而生),和相當大數量的共享狀態在主存中。我們對避免在一些時間中可能被明顯感受到的GC導致的明顯暫停很感興趣。我們打算做一個算法,規模爲共享主存可行的處理器的數目。Sapphire不會導致內存一致性問題,比如額外的內存屏障問題等(詳見附錄E)。

我們將以如下方式組織這篇論文:這個概述部分以一些有用的定義做結束。第二部分是這份報告的核心,討論了收集(collection),詳細闡述了創新之處。第三部分考慮我們如何結合在第二部分中爲了更清楚地表達而分離的不同部分。第四部分將Sapphire與優先工作聯繫起來。第五部分描述了我們的原型工具,測試結果和結論。多個附錄考慮了對主要思想不是必須的更多問題。

內存區域:我們定義了多個內存區域。一個內存區域可能包含多個槽(slot,可能包含指針的內存位置)和非槽數據。1 我們假設內存區中的所有槽都可以被明確地發現。

·U - 堆(很可能被多個線程共享)上面的一個區域,這個區域的對象在一個特定的回收中是不被管制的。U代表Uncollected。爲了方便起見,我們把所有非特定線程不包含對象的槽也包含進U

·C - 堆(很可能被多個線程共享)上面的一個區域,這個區域的對象在一個特定的回收中是被管制的。C代表CollectedC會在之後被分成:

— O - 舊空間:在回收開始的時候就存在的對象副本

— N - 新空間:在回收中倖存的對象的新副本

C只包含對象,也就是說它不像可能含有很多不在對象中的槽的U,它不含裸槽。

·S - 棧:每個線程有一個分離的棧,是每個線程獨有的。S區域包含槽,但是卻不包含對象,也就是說可能沒有從堆對象到棧的指針。2爲方便起見,我們將其他線程本地的槽,比如那些相當於機器寄存器的引用。

對象被回收或否(上文中的UC)是Sapphire主動選擇的。比如可能用變量代空間。Sapphire可以“背”一些它的需要在一個世代的收集器的寫屏障,換句話說,Sapphire需要尋找從UC的指針,以及一個類似記錄集(set)的數據結構來保存掃描U的狀態。

Sapphire和複製回收的一個區別是我們假設新的對象是被分配在U中,而不是在C中。注意,這一點幫助保證標記和複製的結束因爲C是不會增長的。這個決定施加了寫屏障給新創建的對象,因爲Sapphire需要處理他們指向C中對象的槽。

Sapphire是一個純粹的複製回收器,這是準確的,與保守的使用不確定的根相對。可能這是明顯的,但是在Sapphire中,回收器和所有方法線程是同時運行的。

2.Sapphire算法

Sapphire分爲兩個主要的組工作。第一組稱爲標記和複製,(a)確定哪些O中的對象是可以從US區域的根槽達到的,以及(b)在N中建立可以達到的O中對象的副本。在標記和複製過程中,方法線程僅僅讀取和更新O中的對象副本。ON中任意給定對象的副本被保持着鬆散的同步:方法線程對O中對象副本在兩個同步點之間的任何改動將會在進入第二個同步點之前被傳遞到N中的副本。這種方法採取了JVM規範的內存同步法則【12】。這個點,對兩個副本的更新不需要自動、同時地做。一旦我們處於方法線程可以同時監視ON中的副本的回收工作中,如果所有方法線程都在同步點,那麼ON中的副本將會相互一致。我們稱之爲ON空間的動態一致。

第二組工作,稱爲Flip,涉及Filp SU中的指針,使得他們指向N空間而不是O空間。在Sapphire中,這個工作使用僅一個寫屏障(也就是說不使用讀屏障)。Sapphire允許沒有flip的線程同時訪問ON中的副本(使用一個讀屏障),或者確保所有訪問都是到O的對象(然後同時flip所有)。升級的不含讀屏障的flip提供了同時訪問ON中副本的可能性。這種可能性需要稍微緊密一些的同步化來升級兩個副本。

這也會影響指針相等的比較(Java中的==),因爲他必須能夠迴應ON中同一對象的副本的指針在Java語言層面上相等;這種相等比較類似於Brooks實現的eq5】。注意,重要(a)常量null沒有必要做額外工作去比較(b)永遠不要比較兩個位相等的指針或者變量爲null的。這裏有一份==的僞代碼(明顯是內聯的,而且對一個參數爲0的情況進行過優化的);有個叫做flip-pointer-equal應對複雜的場景:

// Pointer Comparison

Pointer-equal(p, q) {

if (p == q) return true;

if (q == 0) return false;

if (p == 0) return false;

return flip-pointer-equal(p, q);

}

flip-pointer-equal調用的確包含了一個高效的讀屏障;然而我們聲稱這是一個罕見操作。

2.1 標記和複製工作:達到動態一致

具體操作是:標記、分配以及複製。注意實際上很多這些操作都被結合並同時進行,這個一會兒會講到。如果我們將其分開,算法的描述將會更清晰。

一種有用的方法去理解這些操作是根據三色標記法(例子在【11】)。在這些規則下,每個槽和對象被認爲是黑色,代表標記且掃描過了,灰色,代表標記了但是沒有必要掃描,或者白色,代表沒有標記。對象中的槽與對象的顏色一樣。一個簡單的條件約束顏色:黑色的槽不會指向白色的對象。Sapphire處理S中的槽位灰色,因此他們可以包含指向任何顏色對象的指針。這說明儲存一個引用在棧的槽裏面不需要任何工作來施加顏色規則。共享內存(全局變量和堆對象)需要工作,用寫屏障的形式。

最初我們認爲所有存在的對象和槽都是白色的,當回收執行的時候,對象的顏色由白色變到灰色,再到黑色。在Sapphire中,黑色的對象是永遠不會變回灰色並重新掃描的。標記工作的目的是給每個可以達到的C中的對象染上黑色。之後,任何不能達到的對象從標記開始一直會保持白色,而且回收器最後會重新聲明他們。新創建的對象被認爲是黑色。

除了動態一致的情況,標記和複製工作本質上是一個同時進行的標記算法(被複制標記過的對象緊跟隨的)。因此,他可以輕易地擴展算法來對待弱引用和最終化的情況。由於他們並不是這篇論文的焦點,我們之後不會討論他們。

標記工作:分爲三個步驟:預標記,安裝標記工作的寫屏障,根標記,處理非棧的根,以及堆/棧標記,完成標記。預標記安裝寫屏障,在這裏以類C僞代碼表示4:

// Mark Phase Write Barrier

// this is only for pointer stores

// the update is *p = q

// the p slot may be in U or 0

// the q object may be in U or 0

mark-phase-barrier(p, q) {

*p = q;

mark-write-barrier(q);

}

mark-write-barrier(q) {

if (old(q) && !marked(q)) {

// old && !marked means “white”

enqueue-object(q);

// enqueue object for collector

// to mark later

} }

注意方法(譯者注:線程)不會進行任何直接標記,而是將對象入隊來讓回收器去標記。認爲入隊的對象都是灰色將會非常有用;那麼這個寫屏障施加了沒有黑色指向白色的規則。

爲什麼入隊而不是直接讓方法線程去標記呢?基本事實,我們將會把標記和複製結合,而且標記步驟將會參與分配新對象副本的空間。讓方法線程去做這個分配會導致一個同步瓶頸。我們通過讓回收器執行分配和複製來避免這個瓶頸。之後,每個方法將會有自己的隊列,入隊將不會有任何同步管理。當回收器掃描一個方法的棧,他也會通過傳遞給一個單個的回收器輸入隊列來清空這個方法的隊列。

根標記步驟迭代U中的槽而且使用mark-write-barrier將所有被這些槽引用的白色的C對象染成灰色。我們認爲這將會使U中的槽變黑。注意關於這個步驟,儲存新分配的對象,包括初始化儲存,都會牽扯到mark-write-barrier,相關的對象會立刻出現在回收器的輸入隊列中。

當它可以掃描U區域來尋找相關的槽,更可能的情況是它使用寫屏障建立的記錄集數據結構來更有效率地定位相關的槽。

在堆/棧標記步驟中,回收器從輸入隊列,一系列的灰色(被標記的)對象,和線程棧開始工作。對每個入隊的對象,回收器檢查此對象是否已經被標記過了。如果是的,回收器丟棄這個隊列的入口;否則,標記這個對象並將其置入清楚的灰色集合用於掃描。對每個灰色集合中的對象,它的槽會被染黑(在這些槽的引用上使用mark-write-barrier),然後這個對象本身會被認爲是黑色的。這是因爲它被標記了,但是並沒有在灰色集合中,所以我們認爲它是黑色的。回收器將會這樣重複地處理,直到輸入隊列和灰色棧都空。

注意一個對象可能會被同一個或多個線程入隊以標記超過一次;然而,最終回收器將會標記它,它不再會被線程入隊。

/棧標記也會涉及發現S中指向O中對象的指針。爲了掃描一個方法線程的棧,收集器簡單地停止方法線程在一個安全點(後面還會講到),然後掃描這個線程的棧(以及寄存器)來尋找對O中白色對象的引用,對每個引用調用標記工作的寫屏障。(它可能使用棧屏障來限制棧的掃描暫停。)當線程被停止的時候,回收器將線程中入隊的對象移動到收集器的隊列中(僅僅使用一些指針同時來更新隊列)。回收器繼續這個線程,然後處理他的隊列,染灰直到這個隊列也變空。

雖然掃描一個獨立線程的棧中指向白色對象的指針是容易的,但是很難見到在所有線程棧中都找不到指向白色對象的指針的情況。關鍵問題是即使一個線程的棧被掃描過了,這個線程仍然能將很多白色的指針進入它的棧,因爲沒有讀屏障來防止這種情況的發生。理解這個解決方法需要知道一個關鍵事實,線程是不能寫入白色的堆對象引用,因爲寫屏障首先會毫無保留地染灰白色的引用。

假設我們在一個特定的時刻t1,以及之後的時刻t2之間掃面了每個線程的棧,沒有任何線程有白色的指針,沒有一個線程有入隊的對象,收集器的隊列和灰色集合一直是空的。我們稱此時在S和標記過的O對象中沒有白色的指針,然後標記工作已經完成。我們觀察到,一個線程只能夠一個(可達到的)灰色對象或者白色對象中獲得白色指針。在t1t2之間沒有任何對象是灰色的,所以一個線程僅僅能夠從白色的對象中獲得白色指針,而且這個線程必須已經擁有了這個對象的指針。但是如果這個線程擁有任何白色的指針,並且在回收器掃面它的棧的時候丟棄掉這些指針的話,他將從此無法再獲得任何白色的指針。這將應用到所有的線程,那麼他們的棧中將不會包含任何白色的指針。

這個主題討論關於可以直接到達的O對象。O對象最開始被U中的槽指向,這些O對象全部被加入到灰色集合中,而且已經被處理了,從t1之後沒有任何O對象被寫屏障添加。一個黑色對象到達白色對象的鏈條一定經過了灰色的對象(因爲三色原則),由於此時沒有灰色的對象,所以所有能到達的O都被標記了。

以下是兩點可能有用的對棧掃描的改進。首先,從上次掃描開始就一直掛起的線程,在此次掃描中沒有必要重複掃描。5第二,如果我們使用了棧屏障【6】,我們可以避免重複掃描從我們上次掃描開始還沒有被線程調用的舊框架。

由於將指針的儲存和與之關聯的寫屏障分離的可能性和必要性,棧掃描要求線程處於GC一致狀態,就是說每個堆儲存的寫屏障已經被執行。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章