推送与移动的成本(堆栈与近存储器)以及函数调用的开销

时间:2012-10-07 06:08:54

标签: c assembly x86 stack processing-efficiency

问题:

访问堆栈的速度与访问内存的速度相同吗?

例如,我可以选择在堆栈中做一些工作,或者我可以直接使用内存中标记的位置工作。

所以,具体来说:push axmov [bx], ax的速度相同吗?同样,pop axmov ax, [bx]的速度相同吗? (假设bx在near内存中占有一个位置。)

提问的动机:

在C中常见的是阻止带参数的琐碎函数。

我一直认为这是因为不仅必须将参数压入堆栈,然后在函数返回后弹出堆栈,还因为函数调用本身必须保留CPU的上下文,这意味着更多的堆栈使用

但假设有人知道标题问题的答案,那么应该可以量化函数用于设置自身的开销(推/弹出/保存上下文等),以等效的直接内存数量表示访问。因此标题问题。

<小时/> (修改:澄清:上面使用的near与16位x86架构的segmented memory model中的far相对。)

2 个答案:

答案 0 :(得分:17)

现在你的C编译器可以超越你。它可以内联简单的函数,如果它这样做,就不会有函数调用或返回,也许,没有额外的堆栈操作与传递和访问形式函数参数有关(或者在函数内联时的等效操作但是如果一切都可以在寄存器中完成,或者更好的是,如果结果是一个常量值,并且编译器可以看到并利用它,那么可用的寄存器就会耗尽。

现代CPU上的函数调用本身可以相对便宜(但不一定是零成本),如果它们被重复,并且有单独的指令缓存和各种预测机制,有助于高效的代码执行。

除此之外,我希望选择“local var vs global var”的性能影响取决于内存使用模式。如果CPU中有内存缓存,则堆栈很可能位于该缓存中,除非您在其上分配和释放大型数组或结构,或者具有深层函数调用或深度递归,从而导致缓存未命中。如果经常访问感兴趣的全局变量或经常访问其邻居,我希望该变量在大多数时间都在缓存中。同样,如果您正在访问无法容纳到缓存中的大量内存,那么您将遇到缓存未命中并可能降低性能(可能因为可能会或可能不会有更好的,缓存友好的方式来执行您的操作想做)。

如果硬件相当笨(没有或没有小的缓存,没有预测,没有指令重新排序,没有推测执行,没有什么),显然你想减少内存压力和函数调用次数,因为每个人都会计算。

另一个因素是指令长度和解码。访问堆栈上位置(相对于堆栈指针)的指令可以比访问给定地址处的任意存储器位置的指令短。更短的指令可以被更快地解码和执行。

我会说所有案例都没有明确的答案,因为表现取决于:

  • 您的硬件
  • 您的编译器
  • 您的程序及其内存访问模式

答案 1 :(得分:12)

对于时钟周期好奇......

对于那些希望看到特定时钟周期的人,instruction / latency tables可用于各种现代x86和x86-64 CPU here(感谢hirschhornsalz指出这些)。

然后,你在奔腾4芯片上得到:

  • push axmov [bx], ax(红色框)在效率上几乎相同,具有相同的延迟和吞吐量。
  • pop axmov ax, [bx](蓝色框)同样高效,尽管mov ax, [bx]的延迟时间是pop ax
  • 的两倍,但吞吐量相同

Pentium 4 Instruction Timing Table

至于评论中的后续问题(第3条评论):

  • 间接寻址(即mov [bx], ax)与直接寻址(即mov [loc], ax)没有实质性的不同,其中loc是保持立即值的变量,例如loc equ 0xfffd

结论:将此与Alexey's thorough answer结合使用,并且有一个非常可靠的例子来说明使用堆栈的效率,并让编译器决定何时应该内联函数。

(旁注:事实上,即使早在1978年的8086之后,使用堆栈的效率仍然不低于相应的mov对内存的效率,如these old 8086 instruction timing tables所示。)


了解延迟时间&amp;可以通过

可能需要更多时间来理解现代CPU的时序表。这些应该有所帮助: