一、棧

  1. 棧通常是一個預定義大小的內存區域,通常約為2M字節左右。
  2. 堆也是一個預定義了默認值的區域。但是它的大小可以隨着程序的執行而增長、改變。
  3. 重要的是,這2個內存區域的實際位置(物理位置)在我們的RAM中完全一樣。
  4. arr 和 a變量的內存數據挨着,兩者之間有一些字節,這只是因為在調試模式下運行,實際上只是添加了些安全守衞(safety guards, 在所有的變量周圍),以確保我們不會溢出所有的變量,在錯誤的內存中訪問它們。
  5. 所以,在內存中,棧變量都捱得很近,因為實際發生的是,當我們在棧中分配變量時,發生的是:棧指針也就是棧頂部的指針,基本上就是移動這麼多字節。
  6. 如果我想分配一個4個字節的整數,我們把指針移動4個字節;如果我想分配一個數組,就像這裏有5個整數,就是4 x 5 = 20字節,最後是Vector3, 有3個int, 3 x 4 = 12 字節,我們只是移動棧指針。
  7. 內存實際上是互相疊加存儲的,就像一個棧。現在大多數棧的實現中,棧是倒着來的,更高的內存地址,存儲第一個變量。
  8. 棧的思路是:把東西堆到一起,這就是為什麼棧分配非常快,它就像一條CPU指令,我們所做的就是移動棧指針,然後我們返回棧指針的地址。
  9. 在棧中,一旦你在棧中分配內存的作用域結束,你在棧中分配的所有內存都會被彈出,會被釋放。
  10. 在棧上分配內存、存儲變量的額外好處:它們在內存中捱得很近,因此,它們可以被放到CPU緩存線(CPU Cache Line:可以理解為CPU Cache中的最小緩存單位)
  11. 如果我們是在堆中創建,可能會產生一些cache miss,(CPU要訪問的數據在緩存中有,稱謂 Cache hit,反之,稱為Cache miss)。相比之下,在棧中分配,可能不會得到cache miss。在我們請求第一個棧上變量之後。
  12. 有一些cache miss 對比 沒有cache miss,不是什麼大問題,你可能完全不會注意到區別,但是如果我們如果要處理上百萬的cache misses, 那就是大問題了。但如果就是少量的cache misses, 區別可以忽略不計。

二、 堆

  1. new 實際上是調用了malloc函數, memory allocate的縮寫,會調用底層操作系統或平台的特定函數,這將在堆上為你分配內存。
  2. 當你啓動你的應用時,你會得到一定數量的物理RAM分配給你。你的程序會維護一個叫做空閒列表(free list)的東西,它是跟蹤哪些內存塊是空閒的,它們在哪兒。
  3. 所以當你需要動態內存的時候,使用動態堆內存,當你使用malloc請求堆內存的時候,它可以瀏覽空閒列表,然後找到一塊空閒內存,至少和你需要的一樣大,然後給你一個它的指針,然後還要記錄一些東西,例如分配的大小,和它現在被分配的情況,你不能再使用那塊內存了(有一堆記錄要做)。
  4. malloc實際實現取決於實現方法,它是一個很大很重的function,需要做很多記錄,不僅僅是得到內存那麼簡單。
  5. 更糟糕的情況是:如果你想要非常多的內存,超過了空閒列表,超過了操作系統給你的初始分配,這個時候,你的程序,你的應用需要詢問你的操作系統,我需要更多的內存,這是非常麻煩的,潛在的成本是巨大的。
  6. 總的來説,在堆上分配內存,有一大堆的事情,而在棧上分配內存,就像一條CPU指令,非常快速。
  7. 我希望大家都明白,事實上,你應該儘量在棧上分配,如果可能的話。
  8. 在堆上分配的唯一原因:是你不能夠在棧上分配。比如需要這個生命週期比函數、或你處理的作用域更長;或者你需要個大數據,比如50M,這就不適合在棧上分配,你不得不在堆上分配。
  9. 只要可以,你就應該在棧上分配內存,因為它就像一條CPU指令一樣,非常快。這是非常非常真實的性能差異。
  10. 性能的不同是因為分配的不同,所以理論上講,如果你預先分配,比如4G內存塊,在你的程序運行之前,在堆上。然後你要從預先分配的4G內存塊中再進行堆數據分配,那麼這就和棧分配基本上一樣了,唯一需要處理的是cpu cache miss (緩存不命中)的問題,但他們的數量可能不夠造成麻煩。
  11. 所以當你new對象時,你需要檢查空閒列表,請求內存,然後記錄這些,這些就是堆相比於棧慢的地方;而實際的訪問(指CPU、緩存)通常可以忽略不計,是通常,但不總是。
  12. 我們可能會討論更多CPU緩存優化的內容,如果你正在編寫一個100萬個元素的集合,然後每個元素都會cache miss,你會看到一個非常真實的性能差異。如果你所有的東西都是連續的或者碎片的。

三、遊戲案例討論

當我們真正開始遊戲引擎系列時,我們講不得不花大量時間討論分配問題,因為這在現實應用中很重要,而現實世界的應用程序是實時的應用程序,所以這對遊戲來説非常重要,不能連續地一幀一幀地分配,因為這會很慢。所以我們必須想出一些聰明的內存管理技術,如果我們想要我們的遊戲更有效率的話。所以我們一定要在遊戲引擎系列討論這個問題。


學習代碼:

#include<iostream>

struct Vector3 {
	int x, y, z;
	Vector3(): x(10), y(20), z(30) {}
};

void testStackHeap() {
	//棧
	int a = 10;
	int arr[5];
	Vector3 v1;

	//堆
	int* heapA = new int(10);
	int* heapArr = new int[5];
	Vector3* heapV = new Vector3();
	
	for (int i = 0; i < 5; i++) {
		arr[i] = i + 1;
	}
	for (int i = 0; i < 5; i++) {
		heapArr[i] = i + 1;
	}
}

int main() {
	testStackHeap();
	std::cin.get();
}