簡體   English   中英

去GC還是不去GC

[英]To GC or Not To GC

我最近看了兩個非常有趣且具有教育意義的語言講座:

Herb Sutter撰寫的第一篇文章介紹了C ++ 0x的所有出色功能,以及為什么C ++的未來比以往任何時候都更加光明,以及M $在這場比賽中被稱為好人。 討論圍繞效率以及如何經常減少堆活動來提高性能。

另一位由Andrei Alexandrescu創作的影片激發了從C / C ++到他的新游戲改變者D過渡 D的大部分內容似乎都非常有動機和設計。 但是,令我感到驚訝的是,D推動了垃圾回收,並且所有類都是僅通過引用創建 更令人困惑的是, 《 D編程語言參考手冊》這本書在有關資源管理的部分中特別指出以下內容:

垃圾收集消除了C和C ++中必需的繁瑣且易於出錯的內存分配跟蹤代碼。 這不僅意味着更快的開發時間和更低的維護成本,而且生成的程序經常運行得更快

這與Sutter關於減少堆活動的不斷討論相矛盾。 我非常尊重薩特(Sutter)和亞歷山大(Alexandrescou)的見解,因此我對這兩個關鍵問題感到困惑

  1. 並非僅通過引用創建類實例會導致大量不必要的堆活動嗎?

  2. 在哪些情況下可以使用垃圾回收而不犧牲運行時性能?

要直接回答您的兩個問題:

  1. 是的,通過引用創建類實例確實會導致大量堆活動, 但是

    一種。 在D中,您struct class struct具有值語義,並且可以執行類中除多態性以外的所有操作。

    b。 由於切片問題,多態性和價值語義從未很好地結合在一起。

    C。 在D中,如果您確實確實需要在一些性能關鍵的代碼中在堆棧上分配一個類實例,而又不關心安全性的損失,則可以通過scoped函數毫不費力地進行操作。

  2. 在以下情況下,GC可以與手動內存管理媲美或比其更快:

    一種。 您仍然在可能的情況下在堆棧上進行分配(就像您通常在D中所做的那樣),而不是依靠堆來進行所有操作(就像您通常在其他GC語言中所做的那樣)。

    b。 您有一個頂級的垃圾收集器(盡管D的當前GC實現在過去的幾個版本中已經進行了一些重大的優化,所以它看起來有些天真,所以它的表現還不如以前的糟糕)。

    C。 您主要是分配小對象。 如果您主要分配大型數組,而性能最終成為問題,則可能需要將其中一些切換到C堆(您可以訪問C的malloc並在D中釋放),或者,如果它具有作用域的生存期,則可以使用其他一些像RegionAllocator這樣的分配器。 (目前正在討論和完善RegionAllocator,以便最終將其包含在D的標准庫中)。

    d。 您不太關心空間效率。 如果使GC運行得過於頻繁而無法使內存占用量過低,則會降低性能。

在堆上創建對象比在堆棧上創建對象慢的原因是,內存分配方法需要處理諸如堆碎片之類的事情。 在堆棧上分配內存就像遞增堆棧指針一樣簡單(恆定時間操作)。

但是,有了緊湊的垃圾收集器,您不必擔心堆碎片,堆分配的速度可以與堆棧分配一樣快。 D編程語言的“ 垃圾收集”頁面對此進行了更詳細的說明。

關於GC語言運行速度更快的斷言可能是假設許多程序在堆上分配內存的頻率比在堆棧上分配頻率高得多。 假設使用GC語言可以更快地分配堆,那么您就可以對大多數程序的大部分進行優化(堆分配)。

1的答案:

只要堆是連續的 ,在堆上分配與在棧上分配一樣便宜。

最重要的是,當您分配彼此相鄰的對象時,內存緩存性能將非常出色。

只要您不必運行垃圾收集器,就不會損失任何性能 ,並且堆保持連續。

那是個好消息:)

答案2):

氣相色譜技術有了很大的進步。 如今,它們甚至可以實時添加。 這意味着保證連續內存是一個策略驅動的,與實現有關的問題。

因此,如果

  • 您可以負擔得起實時gc
  • 您的應用程序中有足夠的分配暫停
  • 它可以使您的自由列表不受限制

您可能會獲得更好的性能。

回答未解決的問題:

如果開發人員從內存管理問題中解脫出來,他們可能有更多時間花在代碼的實際性能和可伸縮性方面。 這也是一個非技術因素

它既不是“垃圾收集”也不是“易於出錯的”手寫代碼。 真正智能的智能指針可以為您提供堆棧語義,這意味着您永遠不會鍵入“刪除”,但您無需為垃圾回收付費。 這是Herb制作的另一個視頻,它表明了這一點-安全又快速-這就是我們想要的。

要考慮的另一點是80:20規則。 您分配的絕大多數位置很可能是無關緊要的,即使您可以將那里的成本降低到零,也不會從GC中獲得太多收益。 如果您接受這一點,那么使用GC所獲得的簡便性將取代使用它的成本。 如果您可以避免進行復制,則尤其如此。 D為80%的情況提供了GC,對於20%的情況提供了訪問堆棧分配和malloc的權限。

即使您擁有理想的垃圾收集器,它仍然比在堆棧上創建東西要慢。 因此,您必須擁有同時允許兩者的語言。 此外,使用垃圾回收器獲得與手動管理的內存分配相同的性能的唯一方法(正確的方法)是使它使用與經驗豐富的開發人員相同的方法來處理內存,並且在許多情況下會要求垃圾收集器在編譯時做出決定,並在運行時執行。 通常,垃圾回收會使事情變慢,僅用於動態內存的語言會變慢,用這些語言編寫的程序的執行可預測性會降低,而執行延遲會更高。 坦白說,我個人不知道為什么需要垃圾收集器。 手動管理內存並不困難。 至少在C ++中沒有。 當然,我不會介意編譯器生成可以為我清理所有事情的代碼,但是目前看來這不可能。

在許多情況下,編譯器可以將堆分配優化回堆棧分配。 如果您的對象沒有逃脫本地作用域,就是這種情況。

在下面的示例中,一個不錯的編譯器幾乎可以肯定會讓x堆棧分配:

void f() {
    Foo* x = new Foo();
    x->doStuff(); // Assuming doStuff doesn't assign 'this' anywhere
    // delete x or assume the GC gets it
}

編譯器所做的工作稱為轉義分析

同樣,D 在理論上可以具有移動的GC ,這意味着當GC將您的堆對象壓縮在一起時,通過改進緩存的使用可能會提高性能。 如傑克·埃德蒙茲(Jack Edmonds)的回答所述,它還可以消除堆碎片。 手動內存管理可以完成類似的操作,但這是額外的工作。

當高優先級任務未運行時,增量低優先級GC將收集垃圾。 高優先級線程將運行得更快,因為將不會進行內存分配。 這是Henriksson的RT Java GC的想法,請參見http://www.oracle.com/technetwork/articles/javase/index-138577.html

實際上,垃圾回收確實會降低代碼速度。 它為您的代碼增加了必須運行的程序的額外功能。 它還存在其他問題,例如,直到實際需要內存后,GC才會運行。 這可能會導致較小的內存泄漏。 另一個問題是,如果未正確刪除參考,GC將不會拾取它,並再次導致泄漏。 我與GC有關的另一個問題是,它在某種程度上促進了程序員的懶惰。 我提倡在進入更高級別之前學習內存管理的低級概念。 就像數學一樣。 您將學習如何求解二次方,或者如何首先手動求導數,然后學習如何在計算器上進行求算。 使用這些東西作為工具,而不是拐杖。

如果您不想降低性能,請對GC以及堆與堆棧的使用情況保持警惕。

我的觀點是,當您進行常規過程編程時,GC不如malloc。 您只需從一個過程轉到另一個過程,進行分配和釋放,使用全局變量,然后聲明一些函數_inline或_register。 這是C風格。

但是,一旦進入更高的抽象層,至少需要引用計數。 因此,您可以通過引用傳遞,計數並在計數器為零時釋放它們。 這很好,並且在對象的數量和層次結構變得難以手動管理之后,優於malloc。 這是C ++風格。 您將定義構造函數和析構函數以遞增計數器,然后進行修改復制,因此,一旦一方修改了共享對象的一部分,但是另一方仍然需要原始值,共享對象將被拆分為兩部分。 因此,您可以在函數之間傳遞大量數據,而無需考慮是在此處復制數據還是在此處發送指針。 引用計數會為您做出這些決定。

然后是整個世界,閉包,函數編程,Duck類型,循環引用,異步執行。 代碼和數據開始混合,您發現自己將函數作為參數傳遞的頻率比普通數據高。 您意識到元編程無需宏或模板即可完成。 您的代碼開始泛濫成災,失去了堅實的基礎,因為您在回調的回調內部執行某些操作,數據變得無根,事物變得異步,您沉迷於閉包變量。 因此,這是基於計時器的內存遍歷GC唯一可行的解​​決方案,否則根本無法進行閉包和循環引用。 這是JavaScript方式。

您提到了D,但是D仍然是C ++的改進,因此您可以選擇在構造函數,堆棧分配,全局變量(即使它們是各種實體的復雜樹)中進行malloc或ref計數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM