国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
.NET垃圾收集器的過去現(xiàn)在和未來(lái)(一)
.NET垃圾收集器的過去、現(xiàn)在和未來(lái)(一)
譯者         程化
 
 
Patrick Dussud介紹:
Patrick Dussud在微軟工作了11年,曾經(jīng)負(fù)責(zé)VBA、Jscript、MS Java等語(yǔ)言運(yùn)行時(shí)的垃圾收集器(Garbage Collector)的設(shè)計(jì),目前負(fù)責(zé).NET CLR垃圾收集器的設(shè)計(jì)。他是.NET CLR的架構(gòu)師,WinFX的首席架構(gòu)師,Windows架構(gòu)師組的成員。
在微軟之前,Patrick是德州儀器(TI)Explorer工作站系統(tǒng)的主要設(shè)計(jì)人,Lucid公司Energize產(chǎn)品的首席架構(gòu)師。
 
Charles:好的,今天我們又回到了42號(hào)樓,采訪對(duì)象是Patrick Dussud,垃圾收集器的創(chuàng)造者。Patrick,最近怎樣?
 
Patrick:很好。
 
Charles:你還沒有上過Channel 9,我們?cè)噲D聯(lián)系你已經(jīng)有些日子了。開始的話題似乎應(yīng)該是,什么是垃圾收集器?我們從這個(gè)最基本的地方開始,垃圾收集器負(fù)責(zé)什么?
 
Patrick:垃圾收集器使用戶內(nèi)存管理自動(dòng)化。在以前的C++中,你必須用“malloc”或者“new”來(lái)分配內(nèi)存,然后在適當(dāng)?shù)臅r(shí)候釋放內(nèi)存。你必須保證在釋放之前內(nèi)存沒有被別人使用,如果你把內(nèi)存給了別人,往往你就不確定應(yīng)該何時(shí)釋放內(nèi)存了。當(dāng)你釋放了內(nèi)存,不知道別人正在使用這塊內(nèi)存時(shí),就產(chǎn)生了程序崩潰的問題。所以,當(dāng)你顯式進(jìn)行“new”和“delete”時(shí),內(nèi)存管理是一個(gè)復(fù)雜的問題,并且,此時(shí)你的代碼不可組合。要么你必須確定對(duì)自己的內(nèi)存有完全的控制,因此,要達(dá)到這種完全隔離的目的,你必須在將內(nèi)存?zhèn)鬟f給別的模塊時(shí)進(jìn)行完全拷貝,這樣,別的模塊就只對(duì)這個(gè)完全拷貝的內(nèi)存負(fù)責(zé)。要么你就得在某個(gè)地方形成對(duì)整個(gè)內(nèi)存池的統(tǒng)一的管理,這就是自動(dòng)化內(nèi)存管理,這就是垃圾收集器的工作。
垃圾收集器本質(zhì)上就是負(fù)責(zé)跟蹤所有對(duì)象被引用到的地方,關(guān)注對(duì)象不再被引用的情況,回收相應(yīng)的內(nèi)存,并且用高效率的方式來(lái)做這件事,很可能其效率甚至高于傳統(tǒng)的“new”和“delete”范疇。事實(shí)上,我們?cè)噲D超過“new”和“delete”,因?yàn)槔占鹘o我們提供了新的機(jī)會(huì),而你不會(huì)對(duì)新機(jī)會(huì)設(shè)置限制。舉個(gè)例子,你必須知道每個(gè)對(duì)象在何處被引用,你必須確定每個(gè)對(duì)象是否真的被引用了。而一旦你做到了這一點(diǎn),你會(huì)發(fā)現(xiàn)自己可以移動(dòng)對(duì)象,壓縮對(duì)象占用的內(nèi)存空間,把對(duì)象在整個(gè)內(nèi)存內(nèi)搬來(lái)搬去,因?yàn)槟阒缹?duì)該對(duì)象的每處引用,你可以修改所有的引用。在C++中這是不可能的。如果我們除了使“delete”自動(dòng)化外,還是象“new”和“delete”那樣管理內(nèi)存,我們一定會(huì)比“new”和“delete”慢,因?yàn)槲覀儍H僅增加了額外的開銷。但是,做了內(nèi)存空間的智能壓縮之后,我們發(fā)現(xiàn)自己的速度能夠超過“new”和“delete”,因?yàn)槲覀兡軌虮3址浅>o湊,從而形成緩存本地化,頁(yè)面本地化等等優(yōu)勢(shì),因此,結(jié)果很好,尤其是對(duì)于非常難以管理的服務(wù)器內(nèi)存來(lái)說更是如此。例如,對(duì)于服務(wù)器堆空間碎片化或者相似的問題來(lái)說,事實(shí)上,我們做得比過去任何嘗試都要好。性能不會(huì)隨著時(shí)間的過去而下降,我們得到了穩(wěn)定的內(nèi)存管理速度。
 
Charles:有趣。很多時(shí)候我們都聽人說,“我愿意寫非托管代碼,我不愿意寫托管代碼,我可不愿意我的對(duì)象被別人控制”。很多C++程序員都這樣想。
 
Patrick:是的,確實(shí)如此。這是對(duì)象的“微管理”問題。這個(gè)問題要靠經(jīng)驗(yàn)甚至信念。當(dāng)進(jìn)行和“去除內(nèi)存”有關(guān)系的操作的時(shí)候,大家對(duì)垃圾收集器感到最不放心。此時(shí)我們要和終止器,以及那些析構(gòu)函數(shù)打交道。,在C++中,“析構(gòu)函數(shù)在你進(jìn)行delete時(shí)被調(diào)用”這點(diǎn)非常確定。對(duì)于我們來(lái)說,由垃圾收集器來(lái)關(guān)注對(duì)象消亡的事情,析構(gòu)函數(shù),其實(shí)就是終止器的調(diào)用時(shí)機(jī)由垃圾收集器決定。很多人對(duì)此非常吃驚。特別地,我們必須注意在析構(gòu)函數(shù)中引用了哪些對(duì)象,因?yàn)楫?dāng)你析構(gòu)若干對(duì)象的時(shí)候,這些對(duì)象的析構(gòu)函數(shù)被調(diào)用的先后順序是無(wú)法預(yù)先確定的。有可能你會(huì)先析構(gòu)底層對(duì)象,然后才析構(gòu)高層對(duì)象,如果高層對(duì)象析構(gòu)時(shí)要對(duì)底層對(duì)象做點(diǎn)額外工作,就會(huì)失敗,因?yàn)榈讓訉?duì)象已經(jīng)被析構(gòu)了。當(dāng)然,底層對(duì)象的內(nèi)存還在,我們對(duì)于內(nèi)存的管理很注意一致性,高層對(duì)象執(zhí)行析構(gòu)代碼時(shí)想要訪問的對(duì)象都可以訪問到,只是這些對(duì)象的狀態(tài)已經(jīng)不能被析構(gòu)函數(shù)改變了。這里必須要非常小心。
舉個(gè)例子,你想用一個(gè)類層次實(shí)現(xiàn)文件系統(tǒng),最底層的類封裝操作系統(tǒng)的文件句柄,當(dāng)文件句柄類不再被引用的時(shí)候,你想在析構(gòu)函數(shù)中關(guān)閉操作系統(tǒng)句柄,從而避免泄漏資源。然后我們搭建高層。如果你做一個(gè)字處理器,往往會(huì)有好幾層對(duì)象,所有的終止化操作層層遞進(jìn),因?yàn)槟阃胍缺4婢彺鎯?nèi)容,這樣當(dāng)最終關(guān)閉文件時(shí),所有被緩存的內(nèi)容都被自動(dòng)寫入了。順帶說一下,這并不是編寫字處理器的好方法,正確的方法應(yīng)該是顯式關(guān)閉文件。對(duì)應(yīng)用程序來(lái)說,第一步往往是區(qū)分對(duì)象的副作用與對(duì)象的生命周期。如果隨著對(duì)象的消亡,有其他東西需要結(jié)束,你應(yīng)該提供顯式的方法。(就這個(gè)例子來(lái)說)當(dāng)你調(diào)用這個(gè)顯式的Close方法時(shí),一切良好。但是,如果你忘記調(diào)用Close了,而你的對(duì)象已經(jīng)沒有被引用了,這個(gè)時(shí)候該怎樣做?本質(zhì)上來(lái)說,如果你的程序不能保證高層對(duì)象能夠在清空緩存時(shí)一直向下處理到文件句柄,如果文件句柄先關(guān)閉了,很明顯會(huì)出問題。我們就面對(duì)這個(gè)問題。我們用一個(gè)簡(jiǎn)單的辦法來(lái)解決這個(gè)問題。我們有一種對(duì)象被稱為“關(guān)鍵終止化對(duì)象”,它封裝了widby(.NET 2)中的OS句柄類,它最后被終止化。當(dāng)我們有一系列對(duì)象需要終止化時(shí),關(guān)鍵終止化對(duì)象最后被終止化,從而直到高層干完工作前,它都可以看到文件句柄。在一般意義上,我們沒有一個(gè)保證機(jī)制,因?yàn)槲覀儾幌胍驗(yàn)榻K止化調(diào)用順序問題引入復(fù)雜的對(duì)象關(guān)系圖。一般說來(lái),終止化代碼沒有調(diào)用順序,我們的簡(jiǎn)單方案只是一個(gè)保險(xiǎn),以防程序員在對(duì)象銷毀時(shí)沒有正確地處理最后的副作用。事實(shí)上,調(diào)試模式下,我們?cè)S多的終止化代碼中都有一句調(diào)用,說如果垃圾收集已經(jīng)開始,而程序又進(jìn)入到了這段終止化代碼中,這就是個(gè)錯(cuò)誤,我們拋出錯(cuò)誤,開發(fā)人員負(fù)責(zé)修改這個(gè)錯(cuò)誤。
 
Charles:很有趣。關(guān)鍵終止化對(duì)象的語(yǔ)義是什么?你們?nèi)绾味x關(guān)鍵終止化對(duì)象?
 
Patrick:我們從關(guān)鍵句柄繼承。這些東西內(nèi)建在CLR里面。大家可以從關(guān)鍵句柄繼承,但是只有系統(tǒng)級(jí)代碼才有這種需求。
 
Charles:讓我們談?wù)凜LR垃圾收集器的歷史,比如,你當(dāng)時(shí)面對(duì)的第一個(gè)挑戰(zhàn)之類的……
 
Patrick:垃圾收集器的歷史是,我寫了微軟絕大部分垃圾收集器。我們寫的第一個(gè)產(chǎn)品級(jí)垃圾收集器現(xiàn)在還在用,那就是Jscript和VBScript的垃圾收集器。
當(dāng)時(shí)我們聚了4個(gè)人,決定利用一些周末搞出Jscript來(lái),因?yàn)槲覀冇X得用Jscript進(jìn)行網(wǎng)頁(yè)編程很酷。很早之前,關(guān)于Perl的工具我們就有過爭(zhēng)論,它對(duì)內(nèi)存進(jìn)行非常顯式的管理,解釋器會(huì)根據(jù)要求生成new和delete。我認(rèn)為,“不,我們必須引入垃圾收集器,因?yàn)槲⒐芾頃?huì)成本過高。”我的一個(gè)朋友說,“好的,我來(lái)寫顯式管理,你寫垃圾收集器,我們看看誰(shuí)的好。”我沒有按時(shí)完成任務(wù),我朋友完成得比我快,因?yàn)轱@式處理delete要好實(shí)現(xiàn)得多。然后我們開始運(yùn)行他寫的代碼,但是發(fā)現(xiàn)代碼的速度太慢了。他說,“好吧,我放棄了,我認(rèn)為你的代碼不會(huì)像我的這樣慢。”然后我完成了垃圾收集器,最終放到了產(chǎn)品中。這個(gè)垃圾收集器非常簡(jiǎn)單,編程上很保守。我們并不知道對(duì)內(nèi)存的所有引用之處,如果有個(gè)整數(shù)湊巧看起來(lái)很像某個(gè)對(duì)象的地址,我們就認(rèn)為對(duì)象還活著。我們很保守,不會(huì)銷毀所有能夠銷毀的對(duì)象,不會(huì)大量移動(dòng)對(duì)象,因?yàn)槿绻幸粋€(gè)整數(shù)實(shí)際上指向某個(gè)對(duì)象,但我們不確定它是否是個(gè)指針,因?yàn)樗雌饋?lái)是個(gè)整數(shù),那我們就不敢改變整數(shù)的內(nèi)容,因?yàn)闆]準(zhǔn)這是價(jià)格啊什么的。這個(gè)垃圾收集器非常有限,也不復(fù)雜。
然后,也是這群朋友一起開始了Java虛擬機(jī)(JVM)——微軟Java虛擬機(jī)的研發(fā)。我為這個(gè)虛擬機(jī)寫了另一個(gè)垃圾收集器。這個(gè)垃圾收集器繼承自Jscript的垃圾收集器,也比較保守。在那個(gè)時(shí)候,所有的JVM都進(jìn)行保守編程。然后,我咨詢了另一個(gè)微軟外的朋友,我們一起討論,“如果我們想做一個(gè)Windows上最棒的垃圾收集器,我們應(yīng)該怎樣做?”于是,我們一起工作,寫了一些規(guī)格說明書,然后我開始實(shí)現(xiàn)。有趣的是,我用的是LISP來(lái)實(shí)現(xiàn),因?yàn)樵谀菚r(shí),LISP有最好的調(diào)試工具,保護(hù)方面也很強(qiáng),比如所有的數(shù)組都有邊界檢查。我們有非常好的調(diào)試器。我用LISP編寫,然后用LISP寫了一個(gè)JVM的模擬器,進(jìn)行調(diào)試,然后寫了一個(gè)轉(zhuǎn)換器,把LISP代碼自動(dòng)轉(zhuǎn)換成C++代碼,那就是新的JVM垃圾收集器的基礎(chǔ)。
 
Charles:寫一個(gè)把LISP轉(zhuǎn)換成C++的轉(zhuǎn)換器對(duì)你來(lái)說是不是個(gè)挑戰(zhàn)?
 
Patrick:不是,因?yàn)槲以瓉?lái)在用LISP的公司工作。我曾經(jīng)在德州儀器工作,開發(fā)TI Explorer。我寫過一個(gè)轉(zhuǎn)換器,把LISP的一種方言Zeta LISP轉(zhuǎn)換成標(biāo)準(zhǔn)LISP。我們轉(zhuǎn)換了所有300萬(wàn)行系統(tǒng)代碼,全部自動(dòng)轉(zhuǎn)換,然后我們拋棄了老的方言。所以,我知道怎樣做這個(gè)工作,這不麻煩。當(dāng)我寫LISP代碼時(shí),我很小心地只用那些方便轉(zhuǎn)換到C++上的功能。所以,轉(zhuǎn)換很直接,因?yàn)槲矣袑慙ISP轉(zhuǎn)換器的經(jīng)驗(yàn)。
 
Charles:當(dāng)然,CLR的垃圾收集器是用C++寫的?
 
Patrick:是的,當(dāng)我們從JVM前進(jìn)到CLR時(shí),我用了部分JVM垃圾收集器作為基礎(chǔ),然后進(jìn)行了大幅優(yōu)化。從我的觀點(diǎn)來(lái)看,寫一個(gè)好的垃圾收集器本質(zhì)上是寫一個(gè)堅(jiān)固的,支持良好機(jī)制的基礎(chǔ)。當(dāng)你發(fā)現(xiàn)了一些能工作的機(jī)制后,在這個(gè)機(jī)制上你不想有太多變化,而機(jī)制之間必須足夠正交化。如果你的架構(gòu)良好,你就可以逐步往上加機(jī)制。在表層上,引入我稱之為“政策”的東西。政策決定在哪些情況下使用何種機(jī)制。垃圾收集器的絕大部分速度和效率都來(lái)源于對(duì)政策的調(diào)整。當(dāng)應(yīng)用程序使用一般機(jī)制時(shí),垃圾收集器會(huì)自動(dòng)發(fā)現(xiàn)工作負(fù)載的增加,然后進(jìn)行調(diào)整,基本上我們會(huì)把應(yīng)用程序從非常無(wú)效的收集模式調(diào)整到更有效的收集模式中。年復(fù)一年,我們都在研究負(fù)載情況,如果某個(gè)負(fù)載看起來(lái)很糟,我們會(huì)問,“糟在何處?我們?nèi)绾尾拍芨纳曝?fù)載情況?”當(dāng)我們找到方法后,我們就知道,啊,當(dāng)發(fā)生這種情況時(shí),應(yīng)該使用這些機(jī)制,這樣就能使負(fù)載好得多。于是,政策就會(huì)力求通過觀察關(guān)聯(lián)因素發(fā)現(xiàn)這些情況。我們觀察所有代齡的收集頻率,我們觀察內(nèi)存內(nèi)部的碎片狀況,我們觀察內(nèi)存占用,我們觀察內(nèi)部的記錄,研究垃圾收集器內(nèi)部哪些東西本來(lái)應(yīng)該不太耗時(shí),但是在特定條件下卻耗時(shí)很多。我們觀察所有這些開銷和頻率。從所有這些我們得到結(jié)論,喔,這種機(jī)制實(shí)際上沒多大用,我們本來(lái)以為在盡量重用內(nèi)存,但是,因?yàn)閮?nèi)存占用太多,我們做了一次完全的垃圾收集,但是,完全的垃圾收集卻沒有什么發(fā)現(xiàn),所以,下一次OS告訴我們內(nèi)存仍然過少的時(shí)候,我們最好不要再次對(duì)應(yīng)用程序進(jìn)行完全的垃圾收集,因?yàn)樯洗魏瓦@次之間沒有發(fā)生什么,我們?nèi)匀徊荒軓耐耆睦占械玫胶锰?。這就是個(gè)動(dòng)態(tài)調(diào)整如何進(jìn)行的例子。事實(shí)上,垃圾收集器體現(xiàn)了我們對(duì)來(lái)自客戶、內(nèi)部、合作伙伴的許許多多工作負(fù)載進(jìn)行深入觀察的經(jīng)驗(yàn)體會(huì)。我們努力找到關(guān)聯(lián)因素,這些因素或者使應(yīng)用程序表現(xiàn)良好——我們會(huì)試圖重現(xiàn)這些因素;或者使應(yīng)用程序表現(xiàn)惡劣——我們會(huì)試圖將應(yīng)用程序調(diào)整到更有效的狀態(tài)。
 
Charles:有趣。我想問一個(gè)問題,什么定義了一個(gè)對(duì)象是否還活著?我們來(lái)談?wù)剬?duì)象的生命周期,以及為什么在像垃圾收集器這樣一個(gè)非顯式的環(huán)境中,開發(fā)人員不用明確指出對(duì)象的結(jié)束。這也正是以前的代碼不可組合的原因。
 
Patrick:我們從頭開始談。如何表達(dá)擁有一個(gè)對(duì)象?我們有局部變量,此時(shí)我們說“object i = new object”,這里的“i”表示對(duì)象。這是一種對(duì)象來(lái)源。另一個(gè)來(lái)源是靜態(tài)變量,講起來(lái)更加復(fù)雜,不太有趣,但是道理一樣,都是句柄,你可以創(chuàng)建自己的句柄。這就是執(zhí)行引擎(EE)擁有對(duì)象的主要方式。顯然,對(duì)象會(huì)擁有其他對(duì)象。這就是樹圖的開始。本質(zhì)上,我們可以把一群對(duì)象看作樹圖,或者一系列的樹圖,這些樹圖的根要么是你棧上所有的變量,要么是你程序擁有的所有靜態(tài)變量。這就是最初的樹集。我們管這叫樹集。在收集的時(shí)候,在EE和垃圾收集器模塊之間有劃分明確的協(xié)議。
 
Charles:EE是執(zhí)行引擎嗎?
 
Patrick:是的,就是CLR。當(dāng)垃圾收集模塊決定要開始收集的時(shí)候,它調(diào)用到EE中,請(qǐng)求停止所有的線程,這樣才可以檢查線程堆棧。EE照此辦理,所有的棧被凍結(jié)。然后垃圾收集器告訴EE,現(xiàn)在你必須遍歷所有的棧和靜態(tài)變量,然后返回最初的樹集。EE中有一個(gè)遍歷模塊負(fù)責(zé)這件事。然后,CLR每次用一個(gè)樹調(diào)用垃圾收集器模塊。垃圾收集器收到樹后,將遍歷編譯器生成的靜態(tài)數(shù)據(jù),這些數(shù)據(jù)告訴我們對(duì)象實(shí)例的哪個(gè)偏移量對(duì)應(yīng)著對(duì)其他對(duì)象的引用。我們挨個(gè)檢查所有的引用位置,對(duì)每個(gè)位置進(jìn)行遞歸檢查。當(dāng)退出遞歸過程的時(shí)候,樹圖中由這個(gè)根出發(fā)能夠到達(dá)的各個(gè)樹都被檢查過了,這個(gè)根能夠到達(dá)的所有地方都被標(biāo)記了。我們用很多方法做標(biāo)記,這個(gè)過程不太有趣。最終,我們能夠說出是否可以到達(dá)某個(gè)對(duì)象,就是靠判斷是否做了標(biāo)記?;镜南敕ň褪橇酎c(diǎn)痕跡,拿著一個(gè)對(duì)象,你能說出它被標(biāo)記了沒有。我們或者在對(duì)象內(nèi)部別人通常不太可能看到的地方寫點(diǎn)東西,或者做一張外部表。我們兩種方法都用,具體用哪種方法看具體情況下的效率。順帶說一句,工作代碼并不按遞歸方式編寫,因?yàn)槟憧赡苡幸粋€(gè)非常、非常長(zhǎng)的檢查鏈,有可能會(huì)耗光棧空間。我們用數(shù)據(jù)棧,只記錄需要檢查的對(duì)象的引用。彈棧,檢查里面的東西,將該對(duì)象的所有引用壓棧,如此反復(fù)直到棧變空為止。棧變空意味著我們已經(jīng)標(biāo)記了這個(gè)根能夠到達(dá)的所有對(duì)象。我們對(duì)所有的局部變量、保存著引用的寄存器、靜態(tài)變量重復(fù)這個(gè)操作。一旦完成,我們就沒有遺漏地標(biāo)記了程序能夠到達(dá)的每一個(gè)對(duì)象。此時(shí),我們就能逐個(gè)對(duì)象地檢查內(nèi)存,發(fā)現(xiàn)它被標(biāo)記了,好的,留下。沒有被標(biāo)記?喔,我們有一個(gè)垃圾了。特定的時(shí)候,我們會(huì)決定是否壓縮所有的垃圾。這就是基本想法。重要的是我們稱之為“完全的垃圾收集”的操作,因?yàn)槲覀儥z查所有的根能夠到達(dá)的所有對(duì)象。我們也有辦法只收集那些最近分配的對(duì)象,我們稱之為“第0代”收集,此時(shí)垃圾收集器只檢查那些最新分配的對(duì)象。因此,我們也要找到一個(gè)辦法,保證如果較老的對(duì)象引用了這些新對(duì)象的話,我們可以知道。我們有辦法很快地找到這些特殊的引用位置,不用在所有的對(duì)象中去遍歷查找。
 
Charles:現(xiàn)在是很好的闡述“代齡”的意思的時(shí)候。對(duì)于垃圾收集器來(lái)說,這是垃圾收集器最近一次查找的垃圾?
 
Patrick:是的,那是最新的一代,我們叫它“第0代”。一般說來(lái),你都會(huì)在這里找到大量的垃圾。它的局部性也很好,緩存中往往有剛剛創(chuàng)建的對(duì)象的引用,如果你幸運(yùn)的話,大部分剛創(chuàng)建的對(duì)象都在緩存中,因此處理起來(lái)很快,進(jìn)行壓縮也很有效率。所以,如果當(dāng)你處理剛剛創(chuàng)建的對(duì)象的時(shí)候,這些對(duì)象在緩存中,并且都過了生命期,你就碰到了最佳情況。實(shí)際情況很少有這樣理想的,但這是你想要首先處理新對(duì)象的動(dòng)力。政策引擎力圖保證這個(gè)過程高效。比如,如果我們發(fā)現(xiàn)第0代沒有垃圾,我們會(huì)說,“哎,也許我們不應(yīng)該頻繁收集,因?yàn)檫@次沒有找到東西,浪費(fèi)了時(shí)間”。反過來(lái),如果找到了很多垃圾,我們會(huì)說,“嘿,太好了,讓我們一會(huì)兒再來(lái)一次。”這是政策引擎力圖保證高效運(yùn)轉(zhuǎn)的方法之一。
 
Charles:幾年之前,關(guān)于“確定性終止化”有過一次大辯論,我曾經(jīng)和C++開發(fā)組的一個(gè)程序員聊過“確定性終止化”,托管C++現(xiàn)在也有某種“確定性終止化”。對(duì)嗎?畢竟C++中有析構(gòu)函數(shù)。
 
Patrick:C++基本上處于混合世界中。如果對(duì)象被顯式地創(chuàng)建和銷毀,它們就不由垃圾收集器管理了,因此,它們需要“確定性終止化”。這些對(duì)象處于自己的世界中,即使將這些對(duì)象加上“__gc”前綴,試圖指出它們是托管對(duì)象,垃圾收集器也幫不上太多忙。關(guān)于這個(gè)問題,我曾經(jīng)用了近6個(gè)月的時(shí)間試圖提供一個(gè)整合的解決方案。最后,我們花了些錢,請(qǐng)Chris Sells幫助我們解決了這個(gè)問題。他用的辦法非常聰明,然而,通過測(cè)量發(fā)現(xiàn),在中等強(qiáng)度的對(duì)象分配過程中,效率上的損失至少為2個(gè)基準(zhǔn)點(diǎn)。所以,當(dāng)垃圾收集器對(duì)應(yīng)用程序作用很大的時(shí)候,你會(huì)付出效率上的損失。但是,在這點(diǎn)上我們不能強(qiáng)求程序員。我們的建議是:不要進(jìn)行微管理,最終,通過這樣或那樣的方式,我們都會(huì)調(diào)用終止器,能夠解決問題。垃圾管理器從整個(gè)內(nèi)存角度出發(fā)考慮問題,試圖使整個(gè)過程高效,而不只局限在某個(gè)特定部分。
 
Charles:我明白了。某種意義上這是一個(gè)通用的管理平臺(tái)。但很有趣的是,既然這是通用平臺(tái),我為什么不能在托管代碼中標(biāo)記出某個(gè)對(duì)象說,我想要自己管理這個(gè)對(duì)象,我會(huì)告訴垃圾收集器這個(gè)對(duì)象何時(shí)生命結(jié)束,然后垃圾管理器才能收集它?你的意思是,垃圾管理器整體掃描,自行收集各個(gè)對(duì)象。
 
Patrick:是的。如果由你來(lái)告訴垃圾收集器,這并不安全,因?yàn)槟憧赡馨褜?duì)象傳給了程序,而你并不知道,這樣一來(lái),你就可能引入讓程序崩潰的Bug。
 
.NET垃圾收集器的過去、現(xiàn)在和未來(lái)(二)
譯者         程化
Charles:想問個(gè)問題,你為什么做垃圾收集器?這個(gè)工作哪點(diǎn)讓你覺得激動(dòng)人心?你做垃圾收集器的歷史是怎樣的?
 
Patrick:對(duì)我來(lái)說,我一直都在做運(yùn)行庫(kù)。很早以前我做LISP,在Schlumberger工作。他們用LISP建立一些很大的系統(tǒng)。我?guī)椭麄儚膬?nèi)部LISP工作站遷移到Deck工作站上,后者在當(dāng)時(shí)運(yùn)行標(biāo)準(zhǔn)LISP。做垃圾收集器的歷史來(lái)源于我在LISP上的工作經(jīng)歷。然后,我在Austin,為德州儀器的Explorer工作,這在當(dāng)時(shí)是一個(gè)受歡迎的LISP工作站。德州儀器的工作涉及運(yùn)行庫(kù)的各個(gè)方面,各種庫(kù)、解釋器、垃圾收集器,等等。然后我在Lucid工作,我們有一個(gè)供Sun工作站C++開發(fā)使用的IDE。為了管理復(fù)雜的對(duì)象交互網(wǎng)絡(luò),我們有一個(gè)內(nèi)存中數(shù)據(jù)庫(kù),專門記錄程序中各個(gè)元素的關(guān)系。比如,一個(gè)函數(shù)調(diào)用了其他五個(gè)函數(shù),我們把這記錄下來(lái),這樣我們就能根據(jù)少數(shù)函數(shù)的變化進(jìn)行增量分析。如果你改變了一個(gè)函數(shù),那依賴于這個(gè)函數(shù)的東西就必須重新編譯,我們能夠跟蹤這種情況。如果你向一個(gè)結(jié)構(gòu)體中添加了一個(gè)成員,所有使用這個(gè)成員,所有知道這個(gè)成員長(zhǎng)度,所有能夠接觸到這個(gè)成員的東西都必須重新編譯,我們也跟蹤這種情況。本質(zhì)上這就是個(gè)跟蹤對(duì)象的大網(wǎng)絡(luò)。事實(shí)上,我們當(dāng)時(shí)沒做一個(gè)垃圾收集器帶來(lái)了大問題,搞得自己很頭疼。有很多情況下我們擁有一個(gè)對(duì)象,刪除了它,但是不清楚影響如何,非常頭疼。在微軟,我開始時(shí)做VB運(yùn)行時(shí)。VB運(yùn)行時(shí)不進(jìn)行收集,但是自動(dòng)管理。它的自動(dòng)管理靠的是自動(dòng)插入AddRef和Release。這套機(jī)制工作得不錯(cuò),唯一的問題是AddRef和Release不可擴(kuò)展。因?yàn)镽elease必須是被鎖定的操作——我們要確保即使有兩個(gè)線程同時(shí)操作,引用計(jì)數(shù)也是正確的。這樣一來(lái),AddRef和Release方式的自動(dòng)內(nèi)存管理就開銷巨大。我做過測(cè)量,大家看見了都說開銷不小,如果在多線程的環(huán)境下工作,這樣的開銷很要命,因?yàn)槲覀冊(cè)诙嗑€程下要做“InterLockedIncrement”和“InterLockedDecrement”,而不是普通的增加和減少引用計(jì)數(shù)。所以,當(dāng)開始為VBScript和Java寫運(yùn)行時(shí)的時(shí)候,我們知道必須要做垃圾收集器了。對(duì)我自己而言,我非常喜歡這個(gè)工作,這可以使你的代碼運(yùn)轉(zhuǎn)如飛,某種意義上垃圾收集器比程序優(yōu)化更具備杠桿作用。如果你有一個(gè)好的優(yōu)化器,將C++程序優(yōu)化提高了5%的性能,你會(huì)說,“哇,太棒了,你知道嗎,程序快了5%!”垃圾收集器能使程序快30%,所以杠桿作用非常明顯。當(dāng)我開始這項(xiàng)工作時(shí),一個(gè)挑戰(zhàn)是服務(wù)器沒有好的垃圾收集器。那個(gè)時(shí)候,垃圾收集器擴(kuò)展性不好。當(dāng)時(shí)的挑戰(zhàn)是做出一個(gè)既可以透明擴(kuò)展,又可以自動(dòng)適應(yīng)不同負(fù)載的垃圾收集器。對(duì)我來(lái)說,這項(xiàng)工作已經(jīng)完成了。我們?cè)谧鲈S多工作,舉個(gè)例子,Channel 9上有個(gè)問題說……
 
Charles:我們來(lái)看看這個(gè)問題,誰(shuí)問的?
 
Patrick:有個(gè)問題問到延時(shí)。有個(gè)Channel 9的網(wǎng)友提到,垃圾收集器是影響托管代碼用于多媒體的原因之一。事實(shí)確實(shí)如此,垃圾收集器會(huì)造成內(nèi)部暫停執(zhí)行。說起來(lái)如果程序內(nèi)部沒有工作,沒有執(zhí)行用戶的代碼,那就是在進(jìn)行垃圾收集。正如我前面說的,原因主要是堆棧,我們必須停止堆棧。我們內(nèi)部使用一種“并發(fā)模式”。并發(fā)模式會(huì)在某些點(diǎn)上暫停程序,當(dāng)然,暫停時(shí)間很短。然而,在許多情況下,我們會(huì)出問題,因此,程序不是暫停很短的時(shí)間,而是暫停較長(zhǎng)的時(shí)間。這也是垃圾收集器目前在解決的問題之一。未來(lái)我們會(huì)引入一種新的并發(fā)收集方式,這是目前在做的很前沿的工作。最終,對(duì)于表現(xiàn)良好的程序來(lái)說,我們會(huì)將暫停時(shí)間控制在幾個(gè)毫秒。目前,找到何種要素能夠代表“表現(xiàn)良好的程序”也是一個(gè)挑戰(zhàn)。我可以寫一個(gè)只創(chuàng)建新對(duì)象而不使用它們的程序,因此,對(duì)象一出生就消亡了,這樣的程序的暫停時(shí)間遠(yuǎn)遠(yuǎn)低于毫秒級(jí)別。問題在于,一旦開始使用這些對(duì)象,一段時(shí)間后,它們就變得難以收集,因?yàn)檫@些對(duì)象和別的仍應(yīng)該生存的對(duì)象攪和在一起,你必須把它們區(qū)分開。最終的區(qū)分手段就是在并發(fā)模式下來(lái)一次完全的垃圾收集,然而這又導(dǎo)致應(yīng)用程序關(guān)鍵工作較長(zhǎng)的暫停。在這方面業(yè)界有許多研究工作,我們?cè)谧约旱姆较蛏线M(jìn)行得也不錯(cuò),未來(lái)數(shù)個(gè)版本就可以體現(xiàn)出來(lái)。
 
Charles:我覺得這個(gè)工作很困難。基本上你是說為了收集某個(gè)執(zhí)行中的進(jìn)程,你必須暫停它,從而能夠訪問內(nèi)存,清除垃圾。
 
Patrick:是的。我們模仿快照方式。如果你只需要暫停幾個(gè)毫秒,來(lái)幅邏輯快照,那就不需要暫停更長(zhǎng)的時(shí)間。問題是,如果在短期內(nèi)你沒有收集到任何東西的話,這個(gè)短期就可能累積起來(lái)。這也是沒有并發(fā)收集時(shí)目前已經(jīng)發(fā)生的情況。第0代沒有收集,第2 代在檢查,應(yīng)用程序一直在檢查。新垃圾在不斷產(chǎn)生,但是未被清除。內(nèi)存使用不斷增長(zhǎng),某個(gè)時(shí)候,我們會(huì)說,停下,我們不能一直這樣,每個(gè)分配內(nèi)存的線程都必須暫停,直到并發(fā)收集完成為止。這就是我們正在解決的問題,目前正在開發(fā)中,我們甚至還不知道整體編譯是否能通過。愿望是美好的,道路是漫長(zhǎng)的。我們還有另一個(gè)頭疼的問題,當(dāng)然也是另一個(gè)機(jī)會(huì)所在,那就是巨大的服務(wù)器內(nèi)存空間。服務(wù)器如果需要進(jìn)行完全的垃圾收集,該收集會(huì)分布到機(jī)器所有的處理器上。到目前為止,趨勢(shì)看起來(lái)一直都是,增加更多的內(nèi)存,而非增加更多的處理器。隨著多核的到來(lái),比如,每個(gè)芯片上有32個(gè)核心,這種趨勢(shì)可能反轉(zhuǎn);但是,直到現(xiàn)在,在64位機(jī)器上增加32G或128G內(nèi)存,要比增加32個(gè)核心容易多了。所以,結(jié)果就是平均每個(gè)核心要管比以前多得多的內(nèi)存。在服務(wù)器上,這將引起比較嚴(yán)重的請(qǐng)求響應(yīng)延時(shí),看起來(lái)就是所有的請(qǐng)求處理都很快,然而時(shí)不時(shí)服務(wù)器會(huì)停止響應(yīng)。當(dāng)完全的垃圾收集發(fā)生時(shí),響應(yīng)會(huì)被阻塞,直到收集完成。有很多方法能減輕這種影響。如果有幾臺(tái)服務(wù)器,而且有一臺(tái)服務(wù)器做基于響應(yīng)時(shí)間的負(fù)載平衡,則負(fù)載平衡服務(wù)器可以自動(dòng)把請(qǐng)求從正在進(jìn)行第2代垃圾收集的服務(wù)器轉(zhuǎn)到別的服務(wù)器上,當(dāng)服務(wù)器可以響應(yīng)之后,負(fù)載平衡服務(wù)器再把請(qǐng)求發(fā)送過來(lái)。所以,這也不是個(gè)致命的問題,然而,這個(gè)問題值得關(guān)注,我們對(duì)這個(gè)問題很感興趣,也在這個(gè)領(lǐng)域進(jìn)行研究。垃圾收集器最美妙的一點(diǎn)就是,這是個(gè)前沿的技術(shù),而且確實(shí)對(duì)人幫助很大。許多人都對(duì)我們?cè)诶占魃系墓ぷ鹘o予了高度評(píng)價(jià),聽起來(lái)確實(shí)讓人舒服。就自己而言,我們知道工作上還有不足;當(dāng)然,我們也在努力做得更好。這不是件做了就扔的事情,這是件你一旦開始,就可以在上面工作許多年的事情。順帶說一句,現(xiàn)在我開發(fā)已經(jīng)干得不多了,我現(xiàn)在是架構(gòu)師。曾經(jīng)我編程非常多,現(xiàn)在編得很少了,我們有個(gè)新的開發(fā)人員,Maoni Steven,她有一個(gè)MSDN Blog - Maoni,非常有趣,講了很多東西,是個(gè)很好的垃圾收集器信息來(lái)源。
 
Charles:太棒了,我應(yīng)該什么時(shí)候去采訪她。你創(chuàng)建了第一個(gè)垃圾收集器,現(xiàn)在還參與得深入嗎?
 
Patrick:是的,我在架構(gòu)未來(lái)的垃圾收集器。
 
Charles:太棒了。對(duì)垃圾收集來(lái)說,你認(rèn)為在未來(lái)會(huì)不會(huì)出現(xiàn)處于垃圾收集管理之下的運(yùn)行時(shí)?那將與現(xiàn)在這種“人工收集的運(yùn)行時(shí)”不同,現(xiàn)在還是程序員寫代碼進(jìn)行管理。
 
Patrick:在服務(wù)器上已經(jīng)是這樣了。微軟內(nèi)部所有的服務(wù)器都在跑托管代碼。我們的MSNBC,某些部分是包給外部公司完成代碼的。他們被服務(wù)器內(nèi)存碎片化問題深深困擾。服務(wù)器在開始的5分鐘跑得非???,然而,每15分鐘就必須重啟一次,因?yàn)閮?nèi)存碎片化太嚴(yán)重了。當(dāng)他們改到ASP.NET上時(shí),呃,ASP.NET執(zhí)行相同的請(qǐng)求,所需要的指令比以前要多,因?yàn)橥泄艽a效率方面有點(diǎn)缺陷,我們一直在努力消除這些低效率之處,然而,生成的代碼還是未能盡善盡美,比如,為了類型安全,就不得不引入一些檢查之類的。但是,他們發(fā)現(xiàn)托管代碼前5分鐘跑得甚至更快,而且可以一直跑下去,不需要重啟。我相信,很明顯,在服務(wù)器上托管代碼更好,這有點(diǎn)像匯編代碼和編譯代碼的關(guān)系。在很小的領(lǐng)域里,匯編代碼可以戰(zhàn)勝編譯代碼,你可以說,“瞧瞧,編譯器在這個(gè)地方笨死了,我可以寫得更好”但是,你不會(huì)用匯編代碼寫整個(gè)程序,如果你這樣做,你一定失敗,因?yàn)橐獙懙臇|西太多了,而且你讓自己陷入了對(duì)整個(gè)程序的所有東西進(jìn)行掌控的境地。我相信垃圾收集器也處于這種位置,我們有許多評(píng)測(cè)指出我們也處于這個(gè)位置。微觀優(yōu)化某個(gè)局部方面,與優(yōu)化整個(gè)程序非常不同。大家應(yīng)該記得,垃圾收集器從整個(gè)應(yīng)用程序的角度來(lái)優(yōu)化,而不是只顧及優(yōu)化某幾個(gè)部分卻傷害了其他部分。
 
Charles:對(duì)特定的應(yīng)用程序來(lái)說,比如你談到過的媒體應(yīng)用程序,某些操作還是需要進(jìn)一步優(yōu)化。
 
Patrick:是的。比如,我們完全支持混合編程模式,你可以在代碼中執(zhí)行非托管代碼,這樣就沒有延時(shí)了,因?yàn)槲覀兺V咕€程,檢查到執(zhí)行的是非托管代碼時(shí),垃圾收集器就立即停止。所以,如果渲染線程執(zhí)行的是非托管代碼,或者是從托管代碼轉(zhuǎn)到非托管代碼,都不會(huì)有延時(shí)。WPF的架構(gòu)就體現(xiàn)了這點(diǎn)。WPF在底層的渲染和上層的圖形對(duì)象模型之間有清晰的劃分。底層渲染由非托管代碼處理,沒有任何延時(shí),所以那兒的動(dòng)畫工作得很好;上層對(duì)象由托管代碼處理,調(diào)用非托管代碼完成渲染。這是個(gè)很好的劃分,工作得很棒。
 
Charles:很棒。我們看看Channel 9上有沒有其他問題?我們對(duì)Patrick Dussud相關(guān)問題進(jìn)行線上即時(shí)搜索,看起來(lái)littlegulu網(wǎng)友有好多問題。
 
Patrick:好的,有個(gè)問題比較有趣。這里大家有個(gè)概念錯(cuò)誤。大家往往認(rèn)為調(diào)用垃圾收集器的collect接口時(shí),垃圾收集器會(huì)決定是否進(jìn)行收集。實(shí)際情況是,如果我們調(diào)用了垃圾收集器的collect接口,這是強(qiáng)制性的,垃圾收集器確實(shí)進(jìn)行收集。實(shí)際上,如果進(jìn)行的是并發(fā)收集,代碼會(huì)立即返回,也許這就是大家為什么會(huì)有誤解的原因,但是垃圾收集確實(shí)啟動(dòng)了。有時(shí)候垃圾收集很快進(jìn)行,但程序過一會(huì)兒才暫停,這是因?yàn)槲覀冊(cè)诓l(fā)模式中,我們開始收集,然后返回。當(dāng)你發(fā)出collect調(diào)用后,收集一定會(huì)發(fā)生。如果你收集的是第0代或第1代,這是非并發(fā)的,代碼在垃圾收集完成后才返回。通常收集耗時(shí)不到1毫秒,對(duì)第1代小于10毫秒,所以調(diào)用執(zhí)行得非???。但是,通常情況下,大家不應(yīng)該顯式調(diào)用。原因是收集器引擎會(huì)觀察收集頻率,收集效率等等,如果發(fā)生了額外的調(diào)用,實(shí)際上會(huì)降低效率。比如,假設(shè)剛剛發(fā)生了一次自然的收集,程序馬上又進(jìn)行顯式收集調(diào)用,這中間很可能只有少量垃圾對(duì)象,因?yàn)榇蠖鄶?shù)對(duì)象才剛剛創(chuàng)建出來(lái)。這樣一來(lái),垃圾收集器就會(huì)認(rèn)為,啊,這太不值得了,也許我們?cè)傧乱淮我膊辉撌占?。這樣一來(lái),垃圾收集器努力保持的自然節(jié)奏就被打亂了。另一個(gè)避免顯式收集的原因是代價(jià)高昂。除非你掌握了整個(gè)應(yīng)用程序的情況,否則很難判斷是否進(jìn)行收集,很難判斷某個(gè)子程序在1秒鐘內(nèi)是否被調(diào)用了100萬(wàn)次,如果你沒有控制程序的所有方面,怎么可能知道呢?所以,如果你是個(gè)類庫(kù),做出判斷,從而進(jìn)行顯式垃圾收集調(diào)用是很困難的。
 
Charles:我想問兩個(gè)問題,一個(gè)是當(dāng)時(shí)你為什么要暴露公共的collect接口?第二個(gè)是當(dāng)我調(diào)用collect時(shí),垃圾收集器僅在我的執(zhí)行環(huán)境中收集嗎?
 
Patrick:收集發(fā)生在所有的地址空間上。如果你的應(yīng)用程序有多個(gè)域,所有的域都會(huì)被同時(shí)收集,所以,這是按進(jìn)程進(jìn)行的,涉及整個(gè)進(jìn)程。我們?yōu)槭裁匆┞哆@個(gè)接口?這個(gè)問題很有趣,這其實(shí)是為了某些資源管理問題。假設(shè)你有某種稀缺資源,比如數(shù)據(jù)庫(kù)連接,如果你需要數(shù)據(jù)庫(kù)連接自動(dòng)消亡,那你就需要一個(gè)機(jī)制啟動(dòng)垃圾收集器。所以我們提供了這個(gè)機(jī)制,用顯式代碼調(diào)用——GC.collect,讓垃圾收集器進(jìn)行收集。我在Blog上還發(fā)現(xiàn)了另一種說法,大家相信,當(dāng)發(fā)現(xiàn)垃圾收集器沒有跟上應(yīng)用程序步伐的時(shí)候,就必須進(jìn)行顯式調(diào)用。通常情況下這不太可能。垃圾收集器被內(nèi)存分配觸發(fā),假如你不斷分配,某次分配會(huì)觸發(fā)垃圾收集。所以,垃圾收集必須跟上應(yīng)用程序的步伐,因?yàn)槔占峁┏绦蜻M(jìn)一步分配的內(nèi)存。所以,如果你在分配內(nèi)存,垃圾收集就不可能不啟動(dòng),最終垃圾收集會(huì)進(jìn)行??雌饋?lái)主要發(fā)生的是兩件事。第一,程序本身可能有泄漏,所以內(nèi)存一直在增長(zhǎng),因?yàn)槟承╈o態(tài)變量引用的是大對(duì)象,而這些對(duì)象一直在增長(zhǎng),比如,這是個(gè)鏈表或者類似的不斷增長(zhǎng)的東西。這時(shí)候,即使你調(diào)用collect也回收不到什么東西。另一個(gè)很隱秘的原因是COM的STA套間。COM的問題是,當(dāng)我們調(diào)用到COM里面時(shí),COM用的是非托管內(nèi)存。對(duì)于用戶來(lái)說,這是透明的,看起來(lái)我們并沒有調(diào)用到COM對(duì)象里面,看起來(lái)就是個(gè)普通的CLR對(duì)象,因?yàn)槲覀冇么硎笴OM對(duì)象變得透明了。某些COM對(duì)象只能在創(chuàng)建它的線程上刪除。如果你的主線程正在忙于創(chuàng)建對(duì)象,這個(gè)線程就沒有時(shí)間在消息隊(duì)列上等待終止器線程的請(qǐng)求,“嘿,你應(yīng)該殺掉這些對(duì)象,因?yàn)樗鼈兪悄銊?chuàng)建的”。這些對(duì)象不會(huì)消失,逐步堆積,所以內(nèi)存使用逐步增長(zhǎng)。看起來(lái)就像是垃圾收集沒有跟上應(yīng)用程序。實(shí)際情況是,垃圾收集積攢了若干終止器線程的請(qǐng)求,而終止器線程必須通過主線程工作,主線程又忙得沒有時(shí)間響應(yīng)終止器線程。通常說來(lái),此時(shí)不需要調(diào)用GC.collect,只要你在終止器上有內(nèi)核對(duì)象的等待,或者分發(fā)了消息,問題就能解決。但是,等待終止器成本較高,要做的工作也不少。最好的解決辦法是不使用COM的STA套間,用MTA套間。但是,如果真有顯式調(diào)用垃圾收集器可以避免內(nèi)存不斷增長(zhǎng)的情況,我們很希望知道,因?yàn)檫@是個(gè)bug,我需要知道這種情況,我們需要修改代碼。
 
Charles:這就帶來(lái)了另一個(gè)話題。你寫的是通用的垃圾收集維護(hù)平臺(tái),這恰好基于無(wú)數(shù)潛在的有關(guān)聯(lián)的對(duì)象,對(duì)象可能是任何類型,它們之間的交互可能非常復(fù)雜。因此,你必須掌握正確地銷毀它們的時(shí)間,這非常有挑戰(zhàn)性。
 
Patrick:這正是我們花費(fèi)了數(shù)年做的事情,這也是政策引擎的作用所在,它就是為了判別我們應(yīng)該啟動(dòng)收集的各種情況,最小化內(nèi)存使用,最大化程序效率。
 
Charles:我推測(cè)垃圾收集器在2000,或2001年就開始運(yùn)行了?給我們講講你當(dāng)時(shí)無(wú)法估計(jì)到的一些有趣的事吧。
 
Patrick:是的。通常,隨著時(shí)間過去,我們會(huì)發(fā)現(xiàn)某個(gè)應(yīng)用程序或者消耗了過多內(nèi)存,或者在垃圾收集時(shí)耗費(fèi)了過多時(shí)間,我們力爭(zhēng)拿到這些程序,測(cè)量它,找到問題所在:是程序的行為怪異?是程序?qū)懛ú粚?duì)?比如,創(chuàng)建了一個(gè)上百兆的樹,刪除,然后不斷重復(fù)這個(gè)過程,此時(shí)程序的基本特點(diǎn)就是要花大量時(shí)間進(jìn)行內(nèi)存管理。是垃圾收集器本來(lái)可以做得更好一點(diǎn),但被這樣那樣的情況蒙蔽?舉例來(lái)說,我們花費(fèi)了大量精力來(lái)處理一個(gè)問題,那就是當(dāng)OS內(nèi)存即將耗盡,內(nèi)存負(fù)載很高時(shí),我們希望能夠保持工作狀態(tài)良好。在這種極限內(nèi)存情況下,我們力圖收集更多內(nèi)存,對(duì)擁有的內(nèi)存用得更節(jié)省,這項(xiàng)工作目前還在進(jìn)行中,雖然不敢說完美,但比以前已經(jīng)做得好多了,我們每天都在進(jìn)步。
 
Charles:你提到過系統(tǒng)的內(nèi)存越多,你的工作就越困難。
 
Patrick:是的,一個(gè)矛盾是OS的效率和所有程序的效率。當(dāng)程序發(fā)生頁(yè)交換的時(shí)候,所有程序的效率都會(huì)下降,因?yàn)轫?yè)交換影響所有人,而且沒有很好的指標(biāo)可以告訴你具體是哪頁(yè)會(huì)被交換出去。如果我們能夠防止頁(yè)交換,犧牲一些CPU時(shí)間換取對(duì)頁(yè)交換的避免是值得的,而不是像現(xiàn)在這樣,在OS和虛擬機(jī)管理器層面上既付出延時(shí),又付出CPU時(shí)間。我們?cè)谶@上面花了很大功夫,我們實(shí)際上要求OS為低內(nèi)存情況提供通知。我們提出的請(qǐng)求得到了滿足,Windows2000實(shí)現(xiàn)了我們的請(qǐng)求。我們這樣使用通知:等待這個(gè)通知,一旦收到通知,我們就試圖切換到節(jié)省模式下。
 
Charles:好的,讓我們?cè)倏匆粋€(gè)問題。我相信你應(yīng)該要回到對(duì)未來(lái)的構(gòu)架工作中去了。
 
Patrick:一個(gè)問題是,“針對(duì)性能敏感的應(yīng)用來(lái)說,最佳實(shí)踐是什么”。創(chuàng)建對(duì)象的開銷很低。我們可以按照內(nèi)存帶寬的速度創(chuàng)建對(duì)象。開銷主要在對(duì)內(nèi)存字節(jié)的處理上,我們必須清理這些字節(jié),保證對(duì)象類型安全,保證內(nèi)部干凈,沒有多余的數(shù)據(jù)。所以,創(chuàng)建對(duì)象是個(gè)很快的過程,但對(duì)象擁有字節(jié)的多寡會(huì)產(chǎn)生重大影響。一個(gè)最佳實(shí)踐是,分配你絕對(duì)需要的最少的內(nèi)存。以前,因?yàn)閳A整到內(nèi)存邊界分配能減少內(nèi)存碎片,我們往往都會(huì)這樣做,“我將分配4字節(jié),然后16字節(jié),然后64字節(jié),因?yàn)樗鼈兇笮≌茫ハ嚆暯樱瑳]有碎片”。垃圾收集器的情況不是這樣。你為分配的每個(gè)字節(jié)付出開銷。所以,分配你需要的最小數(shù)量。第二個(gè)最佳實(shí)踐,保證很容易消亡的對(duì)象回收成本低,回收過程效率高。如果你把這點(diǎn)發(fā)揮到極致,就意味著如果對(duì)象被創(chuàng)建在已經(jīng)被緩存的區(qū)域,并且也在那里消亡,內(nèi)存被全部回收,那對(duì)象就一直在緩存中。正如我之前說過的,實(shí)際情況往往不是這樣,但你可以向這里努力。本質(zhì)上,分配對(duì)象時(shí),如果你能保證除了絕對(duì)要使用的情況外,不更長(zhǎng)時(shí)間地持有對(duì)象,就會(huì)產(chǎn)生好的性能。然而,你還會(huì)有長(zhǎng)期數(shù)據(jù),所以,如果你有在游戲生命期間一直存活,或者近似一直存活的數(shù)據(jù)——比如,數(shù)據(jù)基本穩(wěn)定,只是從游戲的第一階段到第二階段發(fā)生變化,情況也不錯(cuò)。因?yàn)檫@些數(shù)據(jù)在第2代區(qū)域中,而沒有新東西到第2代,因此第2代區(qū)域沒有收集壓力。所以,如果你一方面分配一些非常穩(wěn)定的東西,一方面分配不停產(chǎn)生,很快消亡的對(duì)象,你的情況就非常好——只有非常少的完全的垃圾收集發(fā)生,而眾多的第0代收集效率很高,你不會(huì)損失什么。這是最好的情況。最壞的情況我們稱之為“中年對(duì)象”。它們是足夠老到進(jìn)入第2代,最終又要死的對(duì)象。例如,最壞的一種情況發(fā)生在你剛剛替換了某種緩存后。假設(shè)緩存每10分鐘替換一次,一些老元素被替換掉。這些老元素被保證處于第2代的托管堆中,因?yàn)樗鼈兌甲銐蚶?,被升?jí)到了那里。然后,這些對(duì)象消亡了,你創(chuàng)建了新的對(duì)象來(lái)代替它們。這就不是一個(gè)好機(jī)制,因?yàn)槟阍诘?代區(qū)域引入了新對(duì)象,增加了這部分的收集壓力——這些重要的垃圾必須被收集,所以垃圾收集器將開始自己的工作,這就使性能變?cè)恪?/div>
 
Charles:有趣。舉例來(lái)說,在服務(wù)器環(huán)境下,比如,網(wǎng)站環(huán)境,Channel 9下,有可能有的緩存你不想經(jīng)常過期,然而,一旦過期,就非常影響性能。
 
Patrick:如果只是偶爾發(fā)生,問題不大,那些緩慢的死亡影響性能最大。
 
Charles:我想問的最后一個(gè)問題和Silverlight的到來(lái)有很大關(guān)系,我們現(xiàn)在有一個(gè)精簡(jiǎn)版的CLR,里面的垃圾收集器是怎樣的。
 
Patrick:Silverlight很棒的一點(diǎn)是,它從CLR借用了大量的東西,概念上基本沒有削減。我們有相同的代碼庫(kù),只是不包括所有的文件。所以,其中的垃圾收集器只是工作站版本,沒有服務(wù)器版本。但是,Windows上既有并發(fā)收集也有非并發(fā)收集,Mac版本只有非并發(fā)收集,因?yàn)镸ac不提供實(shí)現(xiàn)高效并發(fā)收集所需要的一些服務(wù)。
 
Charles:這點(diǎn)很有趣,是不是說在其他的平臺(tái)上,OS快沒有內(nèi)存時(shí)就無(wú)計(jì)可施了?因?yàn)槟阍贛ac平臺(tái)上得不到類似Windows上通知內(nèi)存快要耗盡的服務(wù)。
 
Patrick:是的,這是我們無(wú)法得到的一個(gè)服務(wù)。當(dāng)然,對(duì)于Silverlight來(lái)說,有沒有這個(gè)服務(wù)差別不是太大,因?yàn)楫?dāng)托管堆小于16M的時(shí)候,并發(fā)收集一樣不能帶來(lái)太大的幫助。所以,對(duì)大多數(shù)的Silverlight應(yīng)用來(lái)說,垃圾收集器足夠好了。
 
Charles:當(dāng)然。是的,這是個(gè)很棒的垃圾收集器,謝謝你創(chuàng)建了它,我也很期待看到它如何演進(jìn),也許將來(lái)有一天,托管代碼會(huì)像你開始的時(shí)候說的那樣成為可組合的?;谀悻F(xiàn)在做的這些東西,我們可以創(chuàng)建自己的應(yīng)用,不用搞那些基礎(chǔ)的管道建設(shè)了。
 
Patrick:正是如此。我們相信.NET是非常成功的一個(gè)架構(gòu),人們會(huì)大量地使用它。講個(gè)小故事。我們最新的Exchange服務(wù)器,Exchange 12,其代碼絕大多數(shù)都是托管代碼,所有的新代碼都是托管代碼。存儲(chǔ)引擎沒有重寫,還是非托管的,但其余的東西都是托管代碼了。Exchange組告訴我們的消息是,它們將要重寫所有的容器類,因?yàn)楫?dāng)他們寫非托管代碼時(shí),所有的非托管容器類都不能很好地工作,因?yàn)榻M合性不夠好。他們?cè)囘^了STL,MFC,所有這些都不能很好地工作,總有這樣那樣的小問題影響了使用,所以他們要重寫。但是,對(duì)于那些能夠工作的非托管代碼,他們都保留了,基本上底層沒有重寫太多,就是直接使用能夠順利工作的模塊,所以,這是我們的方法的一個(gè)很好的驗(yàn)證。
 
Charles:絕對(duì)的。我應(yīng)該去和Exchange組的人聊聊。謝謝你的時(shí)間,非常感謝,活兒干得很棒,伙計(jì)!
 
Patrick:謝謝!
 
 
 

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=2245734

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服