为什么堆栈内存比 C 中的堆内存"cheaper"?



比较以下两个:

int arr[10]; // stack memory
int* x = malloc(sizeof(int)*10); // heap memory

两者本质上都分配了10个整数变量。然而,我们经常说第一个要便宜得多(分配和释放更快)b/c它只是向前移动堆栈指针。

我们知道,所有程序都在虚拟内存空间中运行,只有程序实际使用的部分才会被分配(也就是映射到物理内存),而未使用的程序则保持虚拟状态。这就是操作系统在不同程序之间共享物理内存的方式。

所以我的问题来了。在我看来,无论你如何分配内存(在堆栈或堆上),有一个共同点是操作系统需要找到&保留一个物理内存块,并将虚拟内存地址(无论是堆栈上还是堆上)映射到物理地址。当系统需要删除映射&释放物理内存。那么,为什么堆栈分配/释放更快呢?内存分配中最大的开销在这两者之间似乎相当公平。

mallocfree的调用通常平均需要几个100到1000的指令,具体取决于实现、堆的当前碎片以及其他细节。

为一个函数分配和释放堆栈帧需要4条指令。

堆栈上的int a[10]基本上是免费的。在x86上(可以是16、32或64位模式),在函数prolog上,当前堆栈指针被复制到基指针;则堆栈指针向下调整(通过SUB通道操作码),使得在基指针和堆栈指针之间有足够的空间来存储该函数帧中的所有局部变量。

由于局部变量未在C中初始化,因此定义

int a[10];

可能不会比少多少开销

int a[432], b[234], c[234], d[123], e[123], f[34], g[23];

例如,对于第一个,编译器可以写导致堆栈指针减去16个字节的序言,而对于第二个,可以写1216个字节的前言。

然后在函数退出时,由于编译器已将旧堆栈指针存储到基指针寄存器,因此基指针寄存器内容将再次存储到堆栈指针寄存器,不需要其他清理。


现在,对于malloc来说,事情就更棘手了。如果程序是多线程的,那么每个线程都需要有一个竞技场/池,或者必须有某种排除来锁定其他线程。malloc需要找到一个足够大的块,然后更新其记账。然后free也必须执行相同的步骤——锁定互斥设备和更新记账。


总之,单个malloc/free对至少有几十条指令的开销,而堆栈变量原则上是免费的。

实际上,这取决于您如何衡量成本。

在现代操作系统中,程序堆栈通常在程序启动期间分配给进程(如果进程的配额在运行时发生更改,则有一些例外)。从那里开始,使用堆栈的程序的操作(例如调用函数、分配auto变量等)非常快速(就程序而言,堆栈位于固定位置)。相比之下,堆在被请求之前不会被分配给程序,因此程序必须在需要时明确地向操作系统请求分配。出于这个原因,就程序而言,与堆相比,堆栈是便宜的——不需要从操作系统请求,不需要等待操作系统响应,程序按顺序使用堆栈(即,如果函数A()调用B(),则堆栈使用是有序的)等。唯一的例外是,程序试图使用比可用堆栈更多的堆栈,但这种考虑也会影响堆。

从现代操作系统的角度来看,在运行的程序中,堆和堆栈之间通常没有任何区别。进程的所有堆和堆栈在物理上都是从同一资源(包括RAM、交换等)中提取的,操作系统需要使用类似的数据结构等来管理所有进程的所有资源的分配和释放。因此,就操作系统而言,差异很小,在为程序分配堆栈或堆时,唯一的区别是,当程序启动时,堆栈被请求一次,当程序退出时,堆被释放一次,而当程序随后运行时,堆(对于许多程序)被请求并释放多次。但操作系统的成本是基于对内存的请求数量和顺序以及释放内存的返回,而不是请求进程将请求分类为堆还是堆栈。

一些编程技术涉及程序在启动期间静态分配所需的所有堆,之后程序再也不会请求堆。从操作系统的角度来看,这样一个程序对堆的请求与对堆栈的请求没有太大区别——就操作系统而言,它们都来自同一内存资源。

较旧的操作系统和硬件对堆和堆栈的管理通常完全不同。在物理上,通常会有区别,因为堆和堆栈位于具有不同性能特征的不同内存芯片中——在实践中,"堆栈"是快速但少量的内存,而堆是较慢但大量的内存。从逻辑上讲,由于堆比堆栈更丰富(并且对它的请求是临时的),系统还需要做更多的工作来跟踪可用堆,并且可能会产生更多的其他成本(例如内存碎片),从而增加堆比堆栈的净成本。

当程序进入一个函数时,会创建一个新的堆栈框架。当一个变量被分配到堆栈上时,它被推送到当前堆栈帧中。当函数退出时,堆栈帧将被解除分配。这种关于何时创建和销毁数据的静态知识使cpu可以轻松地优化堆栈。

另一方面,堆更具动态性。它是一大块内存(而不是许多小的线性帧),由开发人员使用malloc和free进行管理。没有真正的方法来优化这样的内存。

堆分配是使用指针而不是直接引用的,这一事实使这种差异更加突出。每次对堆的访问都需要先进行一次取消引用操作。堆栈上的每次访问都是本地内存,并且很容易缓存在cpu中。

以上所有的细节都太多太复杂了,这里无法涵盖,但这应该为理解提供一个基本的概述。

因为程序的堆栈空间是预先分配的,不涉及系统调用。

堆空间分配需要系统调用,这涉及到对内核的操作链,因此速度较慢。

但是,如果你不像10亿次那样做,它应该是可以忽略的

相关内容

  • 没有找到相关文章

最新更新