【问题标题】:Are stack float array ops faster than heap float ops on modern x86 systems?在现代 x86 系统上,堆栈浮点数组操作是否比堆浮点操作更快?
【发布时间】:2014-09-09 11:09:38
【问题描述】:

在 C 浮点(或双精度)数组上,小到足以放入 L1 或 L2 缓存(大约 16k),并且我在编译时知道谁的大小,在使用它们的函数中定义它们通常会带来速度优势,所以它们是堆栈变量?如果有,差别大吗?我知道过去堆变量比堆栈变量慢得多,但现在由于 cpu 寻址和缓存的结构更加复杂,我不知道这是否属实。

我需要在“块”中对这些数组重复运行浮点数学运算,一遍又一遍地在相同的数组上(大约 1000 次),我想知道是否应该在本地定义它们。我想将它们保持在最近/最快的位置将使我能够更快地反复迭代它们,但我不明白在这种情况下缓存的含义。也许编译器或 cpu 足够聪明,可以意识到我在做什么,并在内部处理循环期间使这些数据数组在硬件上高度本地化,而无需我的干预,也许它在这方面做得比我做得更好。

如果我以这种方式加载大型数组,可能会面临堆栈空间不足的风险?还是堆栈空间在现代系统上没有受到很大限制?数组大小可以在编译时定义,我只需要一个数组和一个 CPU,因为我需要坚持一个线程来完成这项工作。

【问题讨论】:

  • 您是否真的分析了您的代码并发现了性能问题,或者这只是推测性的,即过早优化?
  • 操作的性能没有区别。从处理器的角度来看,内存就是内存。堆栈或堆,没有区别。堆栈的好处是它被有效地缓存并且分配/释放很便宜。
  • 现代系统中的典型堆栈大小在 Mb 范围内 (1-8 Mb)。
  • 一时投机。我已经定时执行测试程序,以大致了解操作的成本 - 不是那么贵,但这是在原子等低功率设备上运行。实际上,当我在周期之间将 cpu 交还给操作系统时,我的大部分问题都是由操作系统延迟引起的,但优化这是未来需要解决的另一项工作。现在我越快让我的处理周期越快,当我的进程再次拥有 cpu 时,我就能更快地赶上。按订单计算,这些操作肯定是我的应用程序中最昂贵的。
  • 缓存效率基本上是我想问的。为了澄清当我在上面定义 16k 时,我的意思是我的数组是 16kb。所以我希望它们保存在尽可能快的内存中,我认为这是缓存,但当然有缓存级别。因此,如果我可以鼓励编译器/cpu 将我的数据保存在最高级别的缓存中,我就赢了。如果在堆栈上定义实现了这一点,我想这就是我的答案,但如果编译器/cpu 在这方面比我聪明得多,并且会缓存它检测到的优化执行、堆栈或堆所需的内容,那么我想我 malloc ?

标签: c arrays performance floating-point


【解决方案1】:

分配和释放速度可能会有所不同。

在堆栈上分配只是从堆栈指针中减去所需的大小,这通常在函数进入时对所有局部变量执行一次,因此它基本上是免费的(除非使用alloca)。同样适用于在堆栈上释放内存。

在堆上分配需要调用mallocnew,这最终会执行一个数量级的更多指令。同样适用于freedelete

一旦分配了数组,访问速度应该没有差异。但是,堆栈很可能已经在 CPU 缓存中,因为之前的函数调用已经为局部变量使用了相同的堆栈内存区域。

如果您的架构采用Non-uniform memory access (NUMA),那么当您的线程被重新安排在与最初分配内存的 CPU 不同的 CPU 上运行时,对不同内存区域的访问速度可能会有所不同。

如需深入了解该主题,请阅读What Every Programmer Should Know About Memory

【讨论】:

  • 关于“堆栈很可能已经在 CPU 缓存中” - 我认为这可能是我的问题的核心。也就是说,我顺序读入数组,然后直接进入处理数组的循环。所以也许这意味着无论哪种方式,缓存都将包含数组(总是),只要我在循环期间没有被换出,它肯定会在那里停留足够长的时间让我处理它。一旦在循环之后写出,我就不需要它了,所以将它刷新到非缓存内存是多余和浪费的,尽管可能无关紧要。
  • @Pete 您可能需要进行基准测试以判断您的工作量是否存在显着差异。
  • 好的,谢谢,如果它接近到我需要对其进行基准测试,我可以这样做,我只是想知道是否有很大的明显差异。
【解决方案2】:

答案是:可能不会。

  1. 在 i7 等现代处理器上,L1/L2/L3 缓存大小为 64K/1MB/8MB,在 4x2 内核之间共享。你的数字有点偏。
  2. 最需要担心的是并行性。如果您可以让所有 8 个内核 100% 运行,那就是一个好的开始。
  3. 堆内存和栈内存没有区别,只是内存而已。堆分配比堆栈分配慢得多,但希望您不要这样做。
  4. 缓存一致性很重要。缓存预取很重要。访问内存中事物的顺序很重要。在这里阅读:https://software.intel.com/en-us/blogs/2009/08/24/what-you-need-to-know-about-prefetching
  5. 但在您进行基准测试之前,所有这些都是无用的。你无法改进你无法衡量的东西。

重新评论:堆栈内存没有什么特别之处。通常重要的事情是将所有数据保持在一起。如果您经常访问局部变量,那么在堆栈上分配它们旁边的数组可能会起作用。如果您有多个堆内存块,那么细分单个分配可能比单独分配更好。您只有阅读生成的代码和基准测试才能知道。

【讨论】:

  • 16k 是我的数据要求,而不是 cpu 缓存,但是 - 因为这是在低功耗硬件上运行的,所以我不会使用 i7 级别的缓存。也就是说,它通常可能在数百 k。由于处理的类型,我不能使用并行,或者至少如果我可以的话,它的复杂性现在对我来说太过分了(现在很可能,而且目前看来是不可能的)。但正如我上面评论的,我想强制我的数据在处理过程中保持在最快的缓存中,直到被写入 IO 硬件。
  • 所以从到目前为止的 cmets 和答案来看,问题真的归结为在堆栈上分配是否会提高在顶层缓存并保持在那里的机会?
  • 如果您有更多信息,最好编辑您的问题,以便所有人都能看到。见编辑。
  • 大卫 - 会的。不确定是否要编辑问题。
【解决方案3】:

平均速度相同。假设数组占用的缓存行没有被其他代码触及。

要确保数组内存对齐至少是 32 位或 64 位对齐(分别用于浮点和双精度),因此数组元素不会跨越缓存线边界。 x86 上的缓存行是 64 字节。

另一个重要因素是确保编译器使用 SSE 指令进行标量浮点运算。这应该是现代编译器的默认值。传统的浮点(也就是 80 位寄存器堆栈的 387)要慢得多,也更难优化。

如果此内存被频繁分配和释放,请尝试通过在池中、全局或堆栈上分配它来减少对 malloc/free 的调用。

【讨论】:

  • 数组可能是 64 位的,所以我想这不是问题?
猜你喜欢
  • 2014-05-27
  • 2020-09-26
  • 2015-09-05
  • 1970-01-01
  • 2016-09-20
  • 1970-01-01
  • 2023-02-02
  • 2015-06-18
  • 1970-01-01
相关资源
最近更新 更多