【问题标题】:What is the cost of creating large local arrays on the stack in C and C++?在 C 和 C++ 中的堆栈上创建大型本地数组的成本是多少?
【发布时间】:2017-02-04 10:00:21
【问题描述】:

假设我有一个从紧密循环中调用的函数,它在一种情况下在堆栈上分配一些大型 POD 数组(无构造函数),而我动态分配数组一次并在每次迭代中重用它们。本地数组是否会增加运行时成本?

据我了解,分配本地 POD 变量归结为移动堆栈指针,因此它应该无关紧要。但是,我想到了一些可能会影响性能的事情:

  • 检查堆栈溢出 - 这些检查由谁以及何时进行,多久检查一次?在某些系统上,堆栈可以自动增长,但同样,我对此知之甚少。

  • 缓存注意事项:CPU 缓存是否以特殊方式处理堆栈,还是与其余数据没有区别?

  • 可变参数数组与上面的有什么不同吗?比如说,对于固定大小的数组,堆栈可以以某种方式预先分配(或由编译器预先计算?),而对于可变参数数组,则涉及其他增加运行时成本的东西。同样,我不知道这是如何工作的。

【问题讨论】:

  • 选择一种语言。它们是不同的语言,具有非常不同的编译器。并指定您的平台
  • C++ 没有变长数组。
  • @kfsone:它们还没有标准语法,但通常可以通过_alloca()获得可变长度自动存储
  • @kfsone clang(也可能是 gcc)允许我轻松混合 C 和 C++ 标准的功能,但在某些情况下您应该禁止编译器警告。
  • @BenVoigt: _alloca() 是一个依赖于平台的 C 函数而不是标准的。它还具有与 VLA 不同的语义。

标签: c arrays variable-length-array


【解决方案1】:

检查堆栈溢出——通常由编译器生成的序言将遍历要使用的空间,步幅对应于页面大小。这保证了如果操作系统准备好扩展堆栈,则对保护页的访问会在任何访问发生之前触发操作系统逻辑。

缓存——堆栈没有以任何特殊方式处理,但由于用于溢出寄存器、保存返回地址等的空间的局部性,您可能会获得更多命中,这会使堆栈在缓存中变热。但是,如果您的堆栈使用量足够大,那么已经在缓存中的部分将只占很小的一部分。此外,堆栈的任何部分最近已被另一个函数使用也可能很热。

可变长度数组/具有运行时限制的数组——并没有那么不同。编译器必须事先计算所需的大小,但是触摸所有页面并调整堆栈指针不会神奇地变得更加昂贵。例外:循环接触页面的展开会受到页面数量不恒定这一事实的影响,但这不太可能产生任何影响。

请注意,有一些平台具有用于返回地址和溢出的专用单独寄存器——关于这些操作使高速缓存中的自动存储变热的注释不适用。

【讨论】:

  • 缓存要复杂得多。没有简单的“堆栈将在缓存中变热”。这也取决于关联性。在堆栈上分配大型数组甚至变得更糟。使用堆栈时对齐也会降低性能。如果没有在现代平台上进行适当的分析,关于行为的特定假设更多的是猜测。
【解决方案2】:

唯一的性能损失是堆栈内存尚未映射。创建虚拟到物理的映射可能需要一些时间,但仅限于您第一次使用内存时。请注意,例如,如果您使用 newmalloc() 或其任何变体在堆上创建了一个大型 POD 数组,并且您获得的内存页面尚未映射,则您还必须付出这个代价然而,要么。

如果堆栈溢出,堆栈溢出检查可能是SIGSEGV。堆栈是否自动增长取决于操作系统,在某些情况下,使用相关堆栈的线程,因为一个进程可以有多个线程,因此可以有多个堆栈。一般来说,进程的原始线程有一个自动增长的堆栈,直到某个限制,而由进程启动的线程有一个固定大小的堆栈。 堆栈如何增长并不像它们必须增长的事实那么重要 - 增长可能会对性能产生重大影响。

因此,通常使用堆栈内存而不是堆内存会快得多 - 只要您预先支付价格并“触摸”您需要用于堆栈的每个页面以确保它“存在”并且具有虚拟到物理的映射。但交易是堆栈内存只能由在该堆栈上运行的线程使用,并且该大小可能比堆更受限制。该线程可能允许其他线程访问其堆栈上的数据,但该线程必须保留对自己堆栈的控制权。

【讨论】:

  • “所以一般来说使用栈内存会快很多”——纯粹的推测,没有真正的基础。这取决于实际应用。事实上,正确合理分配的动态内存可以更快。如果没有特定的用例,就不可能说出来。
  • @Olaf“没有真正基础的纯粹推测”你真的认为仅仅碰撞堆栈指针以获得“更多”堆栈不会“一般......比去快很多”通过malloc()new 调用堆栈及其锁定,定位和分配合适的内存块,并在释放获得的锁时返回它?即使它“正确且合理地分配”,它也不会比向堆栈指针寄存器的内容添加一些值更快。 “但堆栈可能需要增长”好吧,堆也可能需要增长。
  • 1) C 标准不强制执行特定算法。 2)如果您预先分配块,这无关紧要,例如使用游泳池。 3)如果该缓冲区的处理时间占主导地位,则对齐等变得更加重要。还有很多其他问题(RAM 银行、缓存垃圾等)。我没有明确提到分配。然后堆栈可能在不同的内存中,通常更快,但大小有限等。OP 甚至没有指定他的目标拱门。
  • C 标准不强制执行特定算法。 但它确实指定了一个库函数集。同样,调用 functions 怎么能像添加到 register 一样快呢? 如果您预先分配块,这无关紧要,例如使用池。 您也可以预先分配堆栈内存。搜索你周围的空气 - 我怀疑你会注意到一个明显的皱纹。
  • 有趣的是,您专注于一次性开销,而完全忽略了 - 对于非常大的数组 - 更相关的其他惩罚。顺便提一句。函数调用不需要调用函数 - 一旦编译。
猜你喜欢
  • 2018-03-29
  • 2021-12-05
  • 2017-01-11
  • 2015-01-02
  • 2012-10-17
  • 2010-09-24
  • 2011-04-28
  • 2018-06-06
  • 2021-08-08
相关资源
最近更新 更多