5

在这个 VC++ 的反汇编中,正在进行一个函数调用。编译器在推送它们之前将本地指针移动到寄存器:

    memcpy( nodeNewLocation, pNode, sizeCurrentNode );
0041A5DA 8B 45 F8             mov         eax,dword ptr [ebp-8]  
0041A5DD 50                   push        eax  
0041A5DE 8B 4D 0C             mov         ecx,dword ptr [ebp+0Ch]  
0041A5E1 51                   push        ecx  
0041A5E2 8B 55 D4             mov         edx,dword ptr [ebp-2Ch]  
0041A5E5 52                   push        edx  
0041A5E6 E8 67 92 FF FF       call        00413852  
0041A5EB 83 C4 0C             add         esp,0Ch 

为什么不直接推他们?IE

push  dword ptr [ebp-8]

此外,如果您要单独推送,为什么不手动进行。换句话说,不要在上面做“push eax”,而是做

mov [esp], eax

等等,这样做的好处是,在执行 3 次移动之后,您可以执行一次减法来设置新的堆栈指​​针,而不是通过推送隐式减去三次。

更新---发布版本

这是为发布编译的相同代码:

; 741  :    memcpy( nodeNewLocation, pNode, sizeCurrentNode );

  00087 8b 45 f8     mov     eax, DWORD PTR _sizeCurrentNode$[ebp]
  0008a 8b 7b 04     mov     edi, DWORD PTR [ebx+4]
  0008d 50       push    eax
  0008e 56       push    esi
  0008f 57       push    edi
  00090 e8 00 00 00 00   call    _memcpy
  00095 83 c4 0c     add     esp, 12            ; 0000000cH

绝对比调试版本更有效,但它仍然在做 MOV/PUSH 组合。

4

3 回答 3

5

这是一个优化。英特尔处理器手册第 4 卷第 12.3.3.6 节明确提到了这一点:

在英特尔凌动微架构中,使用 PUSH/POP 指令来管理堆栈空间和函数调用/返回之间的地址调整将比使用 ENTER/LEAVE 替代方案更优化。这是因为 PUSH/POP 不需要 MSROM 流,堆栈指针地址更新在 AGU 完成。当被调用函数需要返回给调用者时,被调用者可以发出 POP 指令恢复数据并从 EBP 中恢复堆栈指针。

汇编/编译器编码规则 19。(MH 影响,M 通用性)对于 Intel Atom 处理器,支持 PUSH/POP 的寄存器形式,避免使用 LEAVE;使用 LEA 来调整 ESP 而不是 ADD/SUB。

手册的其余部分并不清楚原因,但它确实提到了隐式 ESP 调整可能出现的 3 周期 AGU 停顿。

于 2012-10-29T16:44:00.267 回答
1

我怀疑它只在调试版本中执行,或者可能在某些情况下通过流水线或其他考虑保证它(例如,它可以将参数放入esi并在调用后使用它)。我查看了一些二进制文件,MSVC 确实使用了这样的推送:

 push ebx          ; mthd
 push dword ptr [ebp+place+4]
 push dword ptr [ebp+place] ; pos
 push [ebp+filedes]   ; fh
 call __lseeki64_nolock

(来自 CRT 的代码)

至于第二个问题,指令寻址esp比推送长:"push eax"是一个字节,"mov [esp-8], eax"而是四个字节。事实上,这种方法(mov而不是push)默认使用 GCC 自几个版本(选项-maccumulate-outgoing-args)以来,它导致代码大小显着增加。据说它使代码更快,但我不相信。

于 2012-10-29T16:24:49.427 回答
1

我实际上已经找到了原因。它与 Pentium MMX 上的指令流水线方式有关。有两条流水线 U 和 V,如果它们是可配对的,则允许 MMX 处理器一次处理 2 条指令。PUSH 不能相互配对,但它们可以与 MOV 配对。所以,如果你写:

mov eax, [indirect]
mov esi, [indirect]
push eax
push esi

然后,发生的情况是指令#1 和#3 配对,#2 和#4 配对,因此,有效地,这四条指令运行的周期数与单个 mov/push 相同,并且单个 mov/push 是比两个 push [indirect] 快。第 4.3 节,第 4 页详细描述了这种确切的情况。41,示例 4.11a 和 4.11b,Agner Fog 的微架构优化指南,可在 Internet 上广泛获取。

于 2012-10-29T20:00:19.033 回答