自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Stack vs Heap:棧區(qū)分配內(nèi)存快還是堆區(qū)分配內(nèi)存快 ?

存儲(chǔ) 存儲(chǔ)架構(gòu)
棧區(qū)是自動(dòng)管理的,堆區(qū)是手動(dòng)管理的,顯然在棧區(qū)上分配內(nèi)存要比在堆區(qū)上更快,當(dāng)在棧區(qū)上申請(qǐng)的內(nèi)存使用場(chǎng)景有限,程序員申請(qǐng)內(nèi)存時(shí)還要更多的依靠堆區(qū),但是在棧區(qū)申請(qǐng)的內(nèi)存滿足要求的情況我個(gè)人更傾向于使用棧區(qū)內(nèi)存。

大家好,我是小風(fēng)哥。

后臺(tái)有讀者問到底是從棧上分配內(nèi)存快還是從堆上分配內(nèi)存快,這是個(gè)比較基礎(chǔ)的問題,今天就來聊一聊。

棧區(qū)的內(nèi)存申請(qǐng)與釋放

毫無(wú)疑問,顯然從棧上分配內(nèi)存更快,因?yàn)閺臈I戏峙鋬?nèi)存僅僅就是棧指針的移動(dòng)而已,這是什么意思呢?什么叫做“棧指針的移動(dòng)”?以x86平臺(tái)為例,在棧上分配內(nèi)存是怎樣實(shí)現(xiàn)的呢?很簡(jiǎn)單,就一行指令:

sub $0x40,%rsp

這行代碼就叫做“棧指針的移動(dòng)”,其本質(zhì)就是這張圖:

很簡(jiǎn)單,寄存器esp中保存的是當(dāng)前棧的棧頂?shù)刂?,由于棧的增長(zhǎng)方向是從高地址到低地址,因此增大棧時(shí)需要將棧指針向下移動(dòng),即sub指令的作用,這條指令將棧頂指針向下移動(dòng)了64字節(jié)(0x40),因此可以說在棧上分配了64字節(jié)。

可以看到,在棧上分配內(nèi)存其實(shí)非常非常簡(jiǎn)單,簡(jiǎn)單到就只有一條機(jī)器指令。

而棧區(qū)的內(nèi)存釋放也非常簡(jiǎn)單,也是只需要一條機(jī)器指令:

leave

leave指令的作用是將?;焚x值給esp,這樣棧指針指向上一個(gè)棧幀的棧頂,然后pop出ebp,這樣ebp就指向上一個(gè)棧幀的棧底:

看到了吧,執(zhí)行完leave指令后ebp以及esp就指向上一個(gè)棧幀了,這就相當(dāng)于棧幀的彈出,pop,這樣stack 1占用的內(nèi)存就無(wú)效了,沒有任何用處了,顯然這就是我們常說的內(nèi)存回收,因此簡(jiǎn)單的一條leave指令就可以回收掉棧區(qū)中的內(nèi)存。

關(guān)于棧、棧幀與棧區(qū),更詳細(xì)的講解可以參考我寫的這篇《??函數(shù)運(yùn)行時(shí)在內(nèi)存中是什么樣子???》。

接下來我們看到堆區(qū)的內(nèi)存申請(qǐng)與釋放。

堆區(qū)的內(nèi)存申請(qǐng)與釋放

與棧區(qū)分配內(nèi)存相對(duì)的是堆內(nèi)存分配,堆區(qū)分配內(nèi)存有多復(fù)雜呢?

在堆區(qū)上申請(qǐng)與釋放內(nèi)存是一個(gè)相對(duì)復(fù)雜的過程,因?yàn)槎驯旧硎切枰绦騿T(內(nèi)存分配器實(shí)現(xiàn)者)自己管理的,而棧是編譯器來維護(hù)的,堆區(qū)的維護(hù)同樣涉及內(nèi)存的分配與釋放,但這里的內(nèi)存分配與釋放顯然不會(huì)像棧區(qū)那樣簡(jiǎn)單,一句話,這里是按需進(jìn)行內(nèi)存的分配與釋放,本質(zhì)在于堆區(qū)中每一塊被分配出去的內(nèi)存其生命周期都不一樣,這是由程序員決定的,我傾向于把內(nèi)存動(dòng)態(tài)分配釋放想象成去停車場(chǎng)找停車位。

這顯然會(huì)讓問題復(fù)雜起來,我們必須小心的維護(hù)哪些內(nèi)存是已經(jīng)分配出去的以及哪些是空閑的、該怎樣找到一塊空閑的內(nèi)存、該怎樣回收程序員不需要的內(nèi)存塊、同時(shí)還不能有嚴(yán)重的內(nèi)存碎片問題,棧區(qū)分配釋放內(nèi)存都無(wú)需關(guān)心這些問題,于此同時(shí)當(dāng)堆區(qū)內(nèi)存空間不足時(shí)還需要擴(kuò)大堆區(qū)等等,這些都使得在堆區(qū)申請(qǐng)內(nèi)存要比在棧區(qū)分配內(nèi)存復(fù)雜的多。

說了這么多,那么在堆區(qū)上申請(qǐng)內(nèi)存要比在棧上申請(qǐng)內(nèi)存慢多少呢?

接下來我們寫段代碼實(shí)驗(yàn)一下。

show me the code

void test_on_stack() {
int a = 10;
}

void test_on_heap() {
int* a = (int*)malloc(sizeof(int));
*a = 10;
free(a);
}

void test() {
auto begin = GetTimeStampInUs();
for (int i = 0; i < 100000000; ++i) {
test_on_stack();
}
cout<<"test on stack "<<((GetTimeStampInUs() - begin) / 1000000.0)<<endl;

begin = GetTimeStampInUs();
for (int i = 0; i < 100000000; ++i) {
test_on_heap();
}
cout<<"test on heap "<<((GetTimeStampInUs() - begin) / 1000000.0)<<endl;
}

這段代碼非常簡(jiǎn)單,這里有兩個(gè)函數(shù):

  • test_on_stack函數(shù)中定義一個(gè)局部變量,這就是從棧上申請(qǐng)一個(gè)整數(shù)大小的內(nèi)存空間
  • test_on_heap函數(shù)從堆上申請(qǐng)一個(gè)整數(shù)大小的內(nèi)存空間

然后我們?cè)跍y(cè)試函數(shù)中分別調(diào)用這兩個(gè)函數(shù),每一個(gè)調(diào)用1億次,記錄下需要運(yùn)行的時(shí)間,得到的測(cè)試結(jié)果為:

test on stack 0.191008
test on heap 20.0215

可以看到,在棧上總耗時(shí)只有大概0.2s,而在堆上分配的耗時(shí)為20s,相差百倍。

值得注意的是,這里在編譯程序時(shí)沒有開啟編譯優(yōu)化,開啟編譯優(yōu)化后的耗時(shí)是這樣的:

test on stack 0.033521
test on heap 0.039294

可以看到,相差無(wú)幾,可這是為什么呢?顯然從常理推斷在棧上分配要更快一些,問題會(huì)出在哪里呢?

既然我們開啟了編譯優(yōu)化,那是不是優(yōu)化后的代碼運(yùn)行的更快了呢,我們來看下編譯優(yōu)化后生成的指令都有啥:

test_on_stackv:
400f85: 55 push %rbp
400f86: 48 89 e5 mov %rsp,%rbp
400f89: 5d pop %rbp
400f8a: c3 retq

test_on_heapv:
400f8b: 55 push %rbp
400f8c: 48 89 e5 mov %rsp,%rbp
400f8f: 5d pop %rbp
400f90: c3 retq

啊哈,編譯器實(shí)在是太聰明了,它顯然注意到這兩個(gè)函數(shù)中的代碼實(shí)際上啥也沒干,即使我們還專門為變量a賦值為了10,但后續(xù)我們根本就沒有用到變量a,因此編譯器給我們生成了一個(gè)空函數(shù),上面這些機(jī)器指令實(shí)際上對(duì)應(yīng)一個(gè)空函數(shù)。

小風(fēng)哥反復(fù)在這里添加代碼都沒有騙過編譯器,我試圖加大變量a賦值的復(fù)雜度,編譯器依然很聰明的生成了一個(gè)空函數(shù),反正我是沒有試出來,可見現(xiàn)代編譯器是足夠智能的,生成的機(jī)器指令效率很高,關(guān)于該怎樣寫出一個(gè)更好的benchmark,從而讓我們可以看到在開啟編譯優(yōu)化的情況下這兩種內(nèi)存分配方式的對(duì)比,歡迎任何對(duì)此有心得或者對(duì)編譯優(yōu)化有心得的同學(xué)留言。

最后讓我們來看看這兩種內(nèi)存分配方式的定位。

棧內(nèi)存與堆內(nèi)存的差異

首先我們必須意識(shí)到,棧是一種先進(jìn)后出的結(jié)構(gòu),棧區(qū)會(huì)隨著函數(shù)調(diào)用層級(jí)的增加而增大,而隨著函數(shù)調(diào)用完成而減少,因此棧是無(wú)需任何“管理”的;與此同時(shí)由于棧的這種性質(zhì),在棧上申請(qǐng)的內(nèi)存其生命周期是和函數(shù)綁定在一起,當(dāng)函數(shù)調(diào)用完成后其占用的棧幀內(nèi)存將無(wú)效,且棧的大小是有限的,你不能在棧上申請(qǐng)過多內(nèi)存,就像這樣一段C代碼:

void test() {
int b[10000000];
b[1000000] = 10;
}

這段代碼運(yùn)行起來后會(huì)core掉,原因就在于棧區(qū)大小是非常有限的,在棧上分配一大塊數(shù)據(jù)會(huì)讓棧撐爆掉,這就是所謂的Stack Overflow:

額。。。不好意思,圖放錯(cuò)了,應(yīng)該是這個(gè)Stack Overflow:

不好意思,又放錯(cuò)了,總之你懂得。

而堆則不同,在堆上分配的內(nèi)存其生命周期是受程序員控制的,程序員決定什么時(shí)候申請(qǐng)內(nèi)存,什么時(shí)候釋放內(nèi)存,因此堆是必須被管理起來的,堆區(qū)是一片很廣闊的區(qū)域,堆區(qū)空間不足時(shí)會(huì)向操作系統(tǒng)請(qǐng)求擴(kuò)大堆區(qū)從而獲得更多地址空間。

當(dāng)然,堆區(qū)在給程序員更大靈活性的同時(shí)需要程序員確保內(nèi)存在不被使用時(shí)釋放掉,否則會(huì)內(nèi)存泄漏,在棧上申請(qǐng)內(nèi)存則不存這個(gè)問題。

總結(jié)

棧區(qū)是自動(dòng)管理的,堆區(qū)是手動(dòng)管理的,顯然在棧區(qū)上分配內(nèi)存要比在堆區(qū)上更快,當(dāng)在棧區(qū)上申請(qǐng)的內(nèi)存使用場(chǎng)景有限,程序員申請(qǐng)內(nèi)存時(shí)還要更多的依靠堆區(qū),但是在棧區(qū)申請(qǐng)的內(nèi)存滿足要求的情況我個(gè)人更傾向于使用棧區(qū)內(nèi)存。

本文轉(zhuǎn)載自微信公眾號(hào)「碼農(nóng)的荒島求生」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系碼農(nóng)的荒島求生公眾號(hào)。

責(zé)任編輯:武曉燕 來源: 碼農(nóng)的荒島求生
相關(guān)推薦

2009-06-03 15:52:34

堆內(nèi)存棧內(nèi)存Java內(nèi)存分配

2013-05-17 15:38:22

iOS開發(fā)iOS堆棧heap stack

2022-05-05 10:00:53

Kafka分區(qū)分配Linux

2021-03-22 11:51:22

Java內(nèi)存棧上

2021-07-14 10:00:32

Python內(nèi)存測(cè)量

2020-05-27 21:13:27

JavaJVM內(nèi)存

2010-09-25 14:12:50

Java內(nèi)存分配

2021-02-28 13:22:54

Java內(nèi)存代碼

2017-03-01 10:45:39

Linux驅(qū)動(dòng)技術(shù)內(nèi)存申請(qǐng)

2013-10-12 13:01:51

Linux運(yùn)維內(nèi)存管理

2022-01-13 10:30:21

C語(yǔ)言內(nèi)存動(dòng)態(tài)

2011-07-15 01:10:13

C++內(nèi)存分配

2018-02-08 14:57:22

對(duì)象內(nèi)存分配

2021-12-16 06:52:33

C語(yǔ)言內(nèi)存分配

2023-10-18 13:31:00

Linux內(nèi)存

2022-03-07 10:54:34

內(nèi)存Linux

2010-09-25 15:40:52

配置JVM內(nèi)存

2021-04-23 07:27:31

內(nèi)存分配CPU

2010-09-17 16:14:22

Java內(nèi)存分配

2013-10-12 11:15:09

Linux運(yùn)維內(nèi)存管理
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)