堆内存优化之动态栈分配
文章目录
本文探索的就是对于堆内存优化的一种技术,动态栈分配(dynamic stack allocations),中心思想就是把在堆上申请内存的操作分配到栈上来完成。
前言
在堆上分配内存的操作开销很大,对性能影响会比较明显。
本文探索的就是对于堆内存优化的一种技术,动态栈分配(dynamic stack allocations),中心思想就是把在堆上申请内存的操作分配到栈上来完成。
主要参考文章在文末链接处附上了,一些翻译可能不太准确的术语可以直接看原文进行理解。
堆 & 栈
在探索动态栈分配之前,先简单描述一下基本知识 —— 堆和栈 的概念。
堆
堆内存在运行时创建,没有固定的地址或大小,一般需要由开发者自行申请分配和释放。
在 Linux 和 Posix 上,一般 malloc 使用 mmap 来提前申请一大块内存,之后再分配使用这块内存中的部分内存,减少 CPU 的负担。
栈
栈内存采用连续的内存地址来进行存放,在编译时就能知道它的大小,按函数调用来进行内存分配,属于临时内存,离开作用域即被释放。
由 SP (stack pointer,栈指针) 进行追踪栈内存,所以开发者不需要操心。
栈内存一般是在线程被创建出来的时候开辟的,通常有大小限制,1MB 左右,超出栈内存深度会引发异常。
动态栈分配 (dynamic stack allocations)
动态栈分配,中心思想就是把在堆上申请内存的操作分配到栈上来完成。
一些值得注意的点是:
-
栈内存同作用域生命周期一致
-
栈内存通常很小,默认大小为 1MB,Linux 上是 8MB
-
在函数参数中无法使用,因为参数也在入栈
-
一般分配超过一页内存(通常 4KB),需要引入 stack probing(栈探针) 来确保栈内存的正常增加
如何使用?
Visual C++ 使用函数 _malloca
来进行栈内存分配,需要传入申请内存的大小,需要与 _freea
配合使用。
Debug 模式下,会分配内存到堆上。
|
|
操作系统是如何操作栈内存的?
这里将描述的是 Windows 平台上的操作。
在进程当中,会有一片虚拟内存是给线程作为栈内存使用的。
一开始,会只有保留的栈内存(reserves)存在,线程根据需要去申请使用(commits),这样避免了虚拟内存的页浪费。
-
保留的栈内存(reserves)是进程中的虚拟内存,是还没被映射到物理内存的,因此还没有相应的物理页内存。
-
申请使用的(commits、已分配的内存)是已经映射到物理内存的,有相应的物理页内存。
这两种内存的大小可以在 PE 文件头进行设置,或者通过开辟线程调用 CreateThread 去申请,通常新线程会有 1MB 的保留内存(reserves) 和用这块内存初始申请的 4KB 的已分配内存(commits)。
为了知悉已分配的栈内存(commits)什么时候会增加,会在最后的已分配的栈内存的内存页会放入哨兵页(guard page)。
如果栈增长触碰到(touched)新的内存页,即哨兵页,会引发异常中断,然后申请使用更多的保留内存,哨兵页同时也挪到最后分配页的后面。
极端案例
哨兵页(guard page)大小也就 4 KB,如果我们申请的栈内存越过了 4KB,也即哨兵页没被触碰到(touched),会发生什么事?
|
|
一旦触碰到(touched)哨兵页后面的内存,那部分内存是未分配的,也即保留内存,这时候进程被终止,引发访问冲突(access violation)。
如何解决极端案例?
需要编译器去保证栈内存的正常增长,编译器引入了栈探针(stack probing)来对付超出 4KB 的栈内存申请。
由于栈内存是静态分配的,所以编译器是可以检测到这种超大的栈内存分配的,对于这种调用栈(stack frame),编译器会生成栈探针(stack probing)指令,实际是调用 _chkstk
函数。
当运用的是动态栈分配(dynamic stack allocations)时,调用的是 _alloca_probe16
函数。
题外:栈内存发生缺页中断会怎样?
当使用 push 指令(入栈操作),如果发生了缺页中断,那么返回地址要保存在哪里呢?
在 x86 架构下,TSS(task state segment,任务状态段) 保存有不同权限(privilege level)下的各自的栈指针(stack pointer),所以当发生用户态和内核态的切换时,系统会根据不同的权限(privilege level)来进入到不同的调用栈当中。
参考链接
https://geidav.wordpress.com/tag/stack-probing/
https://stackoverflow.com/questions/25222967/what-happens-when-a-page-fault-occurs-in-stack
文章作者 calssion
上次更新 2021-05-04