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

Ass*_*him 24 c x86 assembly stack processing-efficiency

题:

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

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

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

问题的动机:

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

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

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


(编辑:澄清:near上面使用的是与16位x86架构far分段内存模型相反.)

Ale*_*nze 17

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

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

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

如果硬件非常愚蠢(没有或没有预测,没有预测,没有指令重新排序,没有推测执行,没有),显然你想减少内存压力和函数调用的数量,因为每个人都会计算.

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

我会说所有情况都没有明确的答案,因为性能取决于:

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

  • @AKE:是的,`mov [bx], ax` 会比 `mov [0x1234], ax` 短。 (2认同)

Ass*_*him 12

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

对于那些希望看到特定时钟周期的用户,可以在这里获得各种现代x86和x86-64 CPU的指令/延迟表(感谢hirschhornsalz指出这些).

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

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

奔腾4指令时序表

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

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

结论:将此与Alexey的完整答案相结合,并且有一个非常可靠的案例来说明使用堆栈的效率并让编译器决定何时应该内联函数.

(旁注:事实上,即使早在1978年的8086之后,使用堆栈的效率仍然不低于相应的mov到内存的效率,从这些旧的8086指令时序表可以看出.)


了解延迟和吞吐量

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