C++学习笔记 22 堆栈的区别
一、栈
-
栈通常是一个预定义大小的内存区域,通常约为2M字节左右。
-
堆也是一个预定义了默认值的区域。但是它的大小可以随着程序的执行而增长、改变。
-
重要的是,这2个内存区域的实际位置(物理位置)在我们的RAM中完全一样。
-
arr 和 a变量的内存数据挨着,两者之间有一些字节,这只是因为在调试模式下运行,实际上只是添加了些安全守卫(safety guards, 在所有的变量周围),以确保我们不会溢出所有的变量,在错误的内存中访问它们。
-
所以,在内存中,栈变量都挨得很近,因为实际发生的是,当我们在栈中分配变量时,发生的是:栈指针也就是栈顶部的指针,基本上就是移动这么多字节。
-
如果我想分配一个4个字节的整数,我们把指针移动4个字节;如果我想分配一个数组,就像这里有5个整数,就是4 x 5 = 20字节,最后是Vector3, 有3个int, 3 x 4 = 12 字节,我们只是移动栈指针。
-
内存实际上是互相叠加存储的,就像一个栈。现在大多数栈的实现中,栈是倒着来的,更高的内存地址,存储第一个变量。
-
栈的思路是:把东西堆到一起,这就是为什么栈分配非常快,它就像一条CPU指令,我们所做的就是移动栈指针,然后我们返回栈指针的地址。
-
在栈中,一旦你在栈中分配内存的作用域结束,你在栈中分配的所有内存都会被弹出,会被释放。
-
在栈上分配内存、存储变量的额外好处:它们在内存中挨得很近,因此,它们可以被放到CPU缓存线(CPU Cache Line:可以理解为CPU Cache中的最小缓存单位)
-
如果我们是在堆中创建,可能会产生一些cache miss,(CPU要访问的数据在缓存中有,称谓 Cache hit,反之,称为Cache miss)。相比之下,在栈中分配,可能不会得到cache miss。在我们请求第一个栈上变量之后。
-
有一些cache miss 对比 没有cache miss,不是什么大问题,你可能完全不会注意到区别,但是如果我们如果要处理上百万的cache misses, 那就是大问题了。但如果就是少量的cache misses, 区别可以忽略不计。
二、 堆
-
new 实际上是调用了malloc函数, memory allocate的缩写,会调用底层操作系统或平台的特定函数,这将在堆上为你分配内存。
-
当你启动你的应用时,你会得到一定数量的物理RAM分配给你。你的程序会维护一个叫做空闲列表(free list)的东西,它是跟踪哪些内存块是空闲的,它们在哪儿。
-
所以当你需要动态内存的时候,使用动态堆内存,当你使用malloc请求堆内存的时候,它可以浏览空闲列表,然后找到一块空闲内存,至少和你需要的一样大,然后给你一个它的指针,然后还要记录一些东西,例如分配的大小,和它现在被分配的情况,你不能再使用那块内存了(有一堆记录要做)。
-
malloc实际实现取决于实现方法,它是一个很大很重的function,需要做很多记录,不仅仅是得到内存那么简单。
-
更糟糕的情况是:如果你想要非常多的内存,超过了空闲列表,超过了操作系统给你的初始分配,这个时候,你的程序,你的应用需要询问你的操作系统,我需要更多的内存,这是非常麻烦的,潜在的成本是巨大的。
-
总的来说,在堆上分配内存,有一大堆的事情,而在栈上分配内存,就像一条CPU指令,非常快速。
-
我希望大家都明白,事实上,你应该尽量在栈上分配,如果可能的话。
-
在堆上分配的唯一原因:是你不能够在栈上分配。比如需要这个生命周期比函数、或你处理的作用域更长;或者你需要个大数据,比如50M,这就不适合在栈上分配,你不得不在堆上分配。
-
只要可以,你就应该在栈上分配内存,因为它就像一条CPU指令一样,非常快。这是非常非常真实的性能差异。
-
性能的不同是因为分配的不同,所以理论上讲,如果你预先分配,比如4G内存块,在你的程序运行之前,在堆上。然后你要从预先分配的4G内存块中再进行堆数据分配,那么这就和栈分配基本上一样了,唯一需要处理的是cpu cache miss (缓存不命中)的问题,但他们的数量可能不够造成麻烦。
-
所以当你new对象时,你需要检查空闲列表,请求内存,然后记录这些,这些就是堆相比于栈慢的地方;而实际的访问(指CPU、缓存)通常可以忽略不计,是通常,但不总是。
-
我们可能会讨论更多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();
}

浙公网安备 33010602011771号