簡體   English   中英

為什么堆上的內存分配比棧上慢得多?

[英]Why is memory allocation on heap MUCH slower than on stack?

我已經被告知很多次了。 但我不知道為什么......從堆分配內存時涉及哪些額外成本? 和硬件有關嗎? 它與CPU周期有關嗎? 這么多的猜測,但沒有確切的答案......有人可以給我一些詳細說明嗎?

正如“unwind”所說,Heap 數據結構比 Stack 更復雜。 在我看來,當線程開始運行時,一些內存空間作為它的堆棧分配給線程,而堆由進程內的所有線程共享。 這種范式需要一些額外的機制來管理每個線程對共享堆的使用,例如垃圾收集。 我說得對嗎?

添加 1 - 2022 年 5 月 13 日上午 10:42

堆棧的管理只涉及指令和寄存器(SP,BP),從某種意義上說,它自然/純粹是硬件。

而對於堆來說,它還涉及復雜的軟件數據結構和算法,其中涉及函數調用(再次涉及堆棧)、內存訪問等。

硬件速度很快,但不如軟件靈活。

軟件很靈活,但不如硬件快。

所以堆並不便宜。

因為堆是比棧復雜得多的數據結構。

對於許多體系結構,在堆棧上分配內存只是改變堆棧指針的問題,即它是一條指令。 在堆上分配內存包括尋找一個足夠大的塊,將其拆分,並管理允許諸如free()之類的東西以不同順序執行的“簿記”。

當范圍(通常是函數)退出時,保證在堆棧上分配的內存被釋放,並且不可能只釋放其中的一部分。

在您重申展開的答案的編輯中,您提到了“堆數據結構”。 要非常小心,因為稱為的數據結構與動態內存分配無關。 為了清楚起見,我將使用free store的更多語言律師術語。

正如已經指出的那樣,堆棧分配需要增加一個指針,該指針通常在大多數架構上都有一個專用寄存器,而解除分配需要相同的工作量。 堆棧分配也適用於特定功能。 這使它們成為編譯器優化的更好候選者,例如預先計算堆棧所需的總空間並為整個堆棧幀執行單個增量。 同樣,堆棧具有更好的數據局部性保證。 堆棧的頂部幾乎總是保證在高速緩存行內,正如我已經提到的,堆棧指針通常存儲在寄存器中。 在某些架構上優化編譯器甚至可以通過重用來自先前堆棧幀的參數來完全消除堆棧上的分配,這些參數作為參數傳遞給更深的堆棧幀中的被調用函數。 同樣,堆棧分配的變量通常也可以提升為寄存器以避免分配。

相比之下,免費商店要復雜得多 我什至不打算開始討論垃圾收集系統,因為那是一個完全不同的話題,而這個問題是關於 C 語言的。 通常,來自空閑存儲的分配和釋放涉及幾種不同的數據結構,例如空閑列表或塊池。 這些數據結構和簿記也需要內存,因此浪費了空間。 此外,簿記記錄經常與分配混合,從而損害其他分配的數據局部性。 來自空閑存儲的分配可能涉及向底層操作系統請求更多的進程內存,通常來自某種形式的slab分配器。

為了進行簡單的比較,並使用 jemalloc-2.2.5 和 sloccount 中的數字作為參考,jemalloc 實現包含超過 8,800 行 C 語言源代碼和另外 700 多行測試代碼。 這應該讓您很好地了解自由存儲分配和堆棧分配之間的復雜性差異:數千行 C 代碼與一條指令。

此外,由於免費存儲分配不限於單個詞法范圍,因此必須跟蹤每個分配的生命周期。 同樣,這些分配可能會跨線程傳遞,因此線程同步問題會進入問題空間。 免費存儲分配的另一個大問題是碎片化。 碎片化會導致很多問題:

  • 碎片化會損害數據的局部性。
  • 碎片化浪費內存。
  • 碎片化使得為大量分配尋找可用空間的工作變得更加困難。

在現代系統中,與自由存儲相比,堆棧通常相對較小,因此自由存儲最終會管理更多空間,從而解決更難的問題。 此外,由於堆棧大小的限制,空閑存儲通常用於較大的分配,必須處理非常大和非常小的分配之間的這種差異也使得空閑存儲的工作更加困難。 通常堆棧分配很小,大約為幾千字節或更少,堆棧的總大小只有幾兆字節。 自由存儲區通常被賦予程序中所有剩余的進程空間 在現代機器上,這可能是幾百 GB,並且免費存儲分配的大小從幾個字節(如短字符串)到 MB 甚至 GB 的任意數據不等,這種情況並不少見。 這意味着自由存儲分配器必須處理底層操作系統的虛擬內存管理。 堆棧分配本質上是計算機硬件內置的。

如果您想真正了解免費存儲分配,我強烈建議您閱讀發表的有關各種 malloc 實現的許多論文和文章中的一些,甚至閱讀代碼。 以下是一些幫助您入門的鏈接:

  • dlmalloc -Doug Lea 的 malloc,在某個時間點用於 GNU C++ 的歷史參考 malloc 實現
  • phkmalloc - 由 Varnish Web 緩存的 Poul-Henning Kamp 編寫的 malloc 的 FreeBSD 實現
  • tcmalloc - 一些 Google 開發人員實現的線程緩存 Malloc
  • jemalloc - Jason Evan 的 FreeBSD 的 malloc 實現(phkmalloc 的繼任者)

以下是一些附加鏈接,其中包含 tcmalloc 實現的描述:

堆棧和堆之間的主要區別在於堆棧上的項目不能亂序刪除。 如果您將項目 A、B、C 添加到堆棧中,則必須先移除 C,否則無法移除 B。 這意味着向堆棧添加新項總是意味着將其添加到堆棧的末尾,這是一個非常簡單的操作。 您只需移動指向堆棧末尾的指針。

另一方面,您可以刪除亂序的項目。 並且只要您事后不在內存中移動其他項目(就像一些垃圾收集堆所做的那樣),那么您的堆中間就會有“洞”。 即,如果您將 A、B、C 添加到堆中並刪除 B,則您的堆在內存中看起來像這樣: A _ C 其中 _ 是一塊未使用的(空閑)內存。 如果您現在添加一個新項目 D,分配器必須找到一個足夠大的連續空閑空間以容納 D。根據您的內存中有多少連續空閑空間,這可能是一項昂貴的操作。 而且它幾乎總是比僅僅移動堆棧的“最后一個元素”指針更昂貴。

在堆棧區域創建數據:只需移動堆棧指針在頭部區域創建數據:在內存池上搜索滿足給定要求的區域(您可以使用第一次擬合,最佳擬合或最差擬合)。找到區域后存儲資料(簿記)

堆棧刪除:堆棧刪除很容易。只需將堆棧指針向下移動堆區域刪除:查找元素在堆上的存儲位置(使用簿記)並在必要時合並兩個相鄰的空閑塊;

暫無
暫無

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

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