Mat*_*lia 7 x86 assembly visual-c++ visual-c++-2010
昨天我看了VC++ 2010生成的一些32位代码(很可能;不知道具体的选项,对不起),我对一个奇怪的反复出现的细节很感兴趣:在许多功能中,它ebx在序言中归零,它总是像"零寄存器"一样使用它(想想$zeroMIPS).特别是,经常:
mov mem,imm大于1到4个字节mov mem,reg(即使对于0也必须编码完整的立即值大小),但通常(gcc)必要的寄存器被"按需"清零,并保持不变为了更有用的目的;cmp reg,ebx.这就是让我感到非常不寻常的事情,因为它应该完全相同test reg,reg,但是增加了对额外寄存器的依赖.现在,请记住,这发生在非叶子函数中,ebx经常被(被调用者)推入堆栈,因此我不相信这种依赖总是完全免费的.此外,它也用于test reg,reg在完全相同的方式(test/ cmp=> jg).最重要的是,"经典"x86上的寄存器是一种稀缺资源,如果你开始泄漏寄存器,你会浪费很多时间没有充分的理由; 为什么要浪费一个通过所有的功能只是为了保持零?(仍然,考虑一下,我不记得在使用这种"零寄存器"模式的函数中看到很多寄存器溢出).
那么:我错过了什么?它是一个编译器blooper还是一些令人难以置信的智能优化,在2010年特别有趣?
这是一段摘录:
; standard prologue: ebp/esp, SEH, overflow protection, ... then:
xor ebx, ebx
mov [ebp+4], ebx ; zero out some locals
mov [ebp], ebx
call function_1
xor ecx, ecx ; ebx _not_ used to zero registers
cmp eax, ebx ; ... but used for compares?! why not test eax,eax?
setnz cl ; what? it goes through cl to check if eax is not zero?
cmp ecx, ebx ; still, why not test ecx,ecx?
jnz function_body
push 123456
call throw_something
function_body:
mov edx, [eax]
mov ecx, eax ; it's not like it was interested in ecx anyway...
mov eax, [edx+0Ch]
call eax ; virtual method call; ebx is preserved but possibly pushed/popped
lea esi, [eax+10h]
mov [ebp+0Ch], esi
mov eax, [ebp+10h]
mov ecx, [eax-0Ch]
xor edi, edi ; ugain, registers are zeroed as usual
mov byte ptr [ebp+4], 1
mov [ebp+8], ecx
cmp ecx, ebx ; why not test ecx,ecx?
jg somewhere
label1:
lea eax, [esi-10h]
mov byte ptr [ebp+4], bl ; ok, uses bl to write a zero to memory
lea ecx, [eax+0Ch]
or edx, 0FFFFFFFFh
lock xadd [ecx], edx
dec edx
test edx, edx ; now it's using the regular test reg,reg!
jg somewhere_else
Run Code Online (Sandbox Code Playgroud)
注意:这个问题的早期版本说它用来mov reg,ebx代替xor ebx,ebx; 这只是我没有正确记住的东西.对不起,如果有人想太多想到了解这一点.
你评论的所有东西看起来都不合适我. test eax,eax将所有标志(AF除外)设置为cmp与零相同,并且对于性能和代码大小是首选.
在P6(PPro到Nehalem)上,读取长死寄存器是不好的,因为它可能导致寄存器读取停顿.P6内核每个时钟只能从永久寄存器文件中读取2或3个最近未修改的架构寄存器(用于获取发布阶段的操作数:ROB保存uops的操作数,不同于SnB系列,它只保存对物理寄存器文件).
由于这是来自VS2010,Sandybridge还没有发布,所以它应该在Pentium II/III,Pentium-M,Core2和Nehalem的调整上投入很大的压力,因为阅读"冷"寄存器是一个可能的瓶颈.
IDK,如果这样的事情对整数寄存器有意义,但我不太了解优化比P6更早的CPU.
cmp/setz/cmp/jnz序列看起来特别引人注目.也许它来自一个编译器内部的固定序列,用于从某些东西产生一个布尔值,它无法优化布尔值的测试,直接回到直接使用标志?这仍然没有解释使用ebx零寄存器,这在那里也是完全无用的.
是否有可能其中一些是来自inline-asm返回一个布尔整数(使用一个想要在寄存器中为零的傻)?
或者源代码可能正在比较两个未知值,并且只有在内联和常量传播之后它才会变成与零的比较?哪个MSVC未能完全优化,所以它仍然保持0作为寄存器中的常量而不是使用test?
(其余部分是在包含代码的问题之前编写的).
听起来很奇怪,或者像CSE /持续吊装的情况一样.即0像处理你想要加载一次的任何其他常量一样处理,然后在整个函数中进行reg-reg复制.
您对数据依赖行为的分析是正确的:从一段时间归零的寄存器移动基本上会启动一个新的依赖链.
当gcc想要两个归零寄存器时,它通常为xor-zeroes,然后使用mov或movdqa复制到另一个.
这在Sandybridge 上是次优的,其中xor- zeroing 不需要执行端口,但可能在Bulldozer系列上获胜,mov可以在AGU或ALU上运行,但xor-zeroing仍需要ALU端口.
对于向量移动,它是Bulldozer的明显胜利:在寄存器重命名中处理,没有执行单元.但是XMM或YMM寄存器的xor- zeroing仍然需要Bulldozer系列上的执行端口(或者对于ymm,需要两个执行端口,所以总是使用带有隐式零扩展的xmm).
尽管如此,我认为这并不能证明在整个函数的持续时间内绑定一个寄存器,尤其是如果它需要额外的保存/恢复.而不是P6系列CPU,其中寄存器读取停顿是一件事.
| 归档时间: |
|
| 查看次数: |
152 次 |
| 最近记录: |