2012-10-29 67 views
5

在VC++的反汇编中,正在进行函数调用。编译器在推送寄存器之前将本地指针指向寄存器:为什么VC + +编译器MOV + PUSH参数而不是只推动它们? x86

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 

为什么不直接推它们?即

push dword ptr [ebp-8] 

此外,如果你要做一个单独的推,为什么不做手动。换句话说,而不是做“推EAX”上面,做

mov [esp], eax 

等等这样做的好处是,做3个MOVS后,你可以做一个减法来设置新的堆栈指​​针,而不是含蓄用推动减去三次。

UPDATE --- Release版本

这是编译版本相同的代码:

; 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组合。

+2

这是实际编译在发布模式?它看起来模糊调试 – harold

+0

它被编译为调试。为什么在这种情况下会有所作为? –

+1

因为编译器不会在调试模式下关心这些事情。 – harold

回答

1

其实我找出了原因。它与指令在奔腾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比两个[间接]快。这个确切的情况将在第4.3节,p。 41,Agner Fog的Microarchitecture优化指南的实例4.11a和4.11b,可以在因特网上广泛获得。

+3

他们实际上不会在这里配对,因为他们必须在PMMX中相邻。如果MSVC仍在针对PMMX进行优化,我也会感到非常惊讶。 – harold

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)并且导致了notable increases in code size。据说它使代码更快,但我不相信。

+0

我显示发布版本ASM,它具有类似的MOV。在查看项目的代码时,编译器从不进行间接推送,只有MOV。我的怀疑是,这可能是为了支持x86的一个非常旧的版本,如286或386或其他东西。 –

+0

@TylerDurden:每个具有32位寄存器的x86系列成员都有“PUSH内存”指令。但是它们通常比MOV-PUSH组合执行速度慢,因为现代微架构并不真正喜欢具有多个内存访问的指令。 –

5

这是一个优化。它是在英特尔处理器手册明确提及的,第4卷,部分12.3.3.6:

在的Intel Atom微架构,使用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失速。

+0

这不是一个真正的Atom问题。我使用VS 10,它是在Atom存在之前编写的编译器。此外,Atom主要是笔记本电脑等低功耗架构。 –

+0

这是一个微架构问题,Atom只是实现它的许多芯片中的一个。 –

相关问题