我在最近关于array[i++]
vs array[i]; i++
的假定速度的争论中写了这个片段。
int array[10];
int main(){
int i=0;
while(i < 10){
array[i] = 0;
i++;
}
return 0;
}
编译器资源管理器中的代码段:https://godbolt.org/g/de7TY2
正如预期的那样,编译器输出array[i++]
和array[i]; i++
的asm相同,且至少为-O1
。然而令我感到惊讶的是xor eax, eax
似乎随机地放置在更高优化级别的函数中。
在-O2
,GCC按预期将xor
放在ret
之前
mov DWORD PTR [rax], 0
add rax, 4
cmp rax, OFFSET FLAT:array+40
jne .L2
xor eax, eax
ret
但是它会在第二个mov
之后将xor放在-O3
mov QWORD PTR array[rip], 0
mov QWORD PTR array[rip+8], 0
xor eax, eax
mov QWORD PTR array[rip+16], 0
mov QWORD PTR array[rip+24], 0
mov QWORD PTR array[rip+32], 0
ret
icc通常将其放在-O1
:
push rsi
xor esi, esi
push 3
pop rdi
call __intel_new_feature_proc_init
stmxcsr DWORD PTR [rsp]
xor eax, eax
or DWORD PTR [rsp], 32832
ldmxcsr DWORD PTR [rsp]
..B1.2:
mov DWORD PTR [array+rax*4], 0
inc rax
cmp rax, 10
jl ..B1.2
xor eax, eax
pop rcx
ret
但是在-O2
push rbp
mov rbp, rsp
and rsp, -128
sub rsp, 128
xor esi, esi
mov edi, 3
call __intel_new_feature_proc_init
stmxcsr DWORD PTR [rsp]
pxor xmm0, xmm0
xor eax, eax
or DWORD PTR [rsp], 32832
ldmxcsr DWORD PTR [rsp]
movdqu XMMWORD PTR array[rip], xmm0
movdqu XMMWORD PTR 16+array[rip], xmm0
mov DWORD PTR 32+array[rip], eax
mov DWORD PTR 36+array[rip], eax
mov rsp, rbp
pop rbp
ret
和-O3
and rsp, -128
sub rsp, 128
mov edi, 3
call __intel_new_proc_init
stmxcsr DWORD PTR [rsp]
xor eax, eax
or DWORD PTR [rsp], 32832
ldmxcsr DWORD PTR [rsp]
mov rsp, rbp
pop rbp
ret
只有clang会将xor
直接放在ret
所有优化级别的前面:
xorps xmm0, xmm0
movaps xmmword ptr [rip + array+16], xmm0
movaps xmmword ptr [rip + array], xmm0
mov qword ptr [rip + array+32], 0
xor eax, eax
ret
由于GCC和ICC都在更高的优化级别上这样做,我认为必定有某种充分的理由。
为什么有些编译器会这样做?
代码在语义上是相同的,编译器可以按照自己的意愿对其进行重新排序,但由于这只会在更高的优化级别进行更改,因此必须由某种优化引起。
答案 0 :(得分:5)
由于eax
未被使用,编译器可以随时将寄存器归零,并且按预期工作。
您没有注意到的一件有趣的事情是icc
-O2
版本:
xor eax, eax
or DWORD PTR [rsp], 32832
ldmxcsr DWORD PTR [rsp]
movdqu XMMWORD PTR array[rip], xmm0
movdqu XMMWORD PTR 16+array[rip], xmm0
mov DWORD PTR 32+array[rip], eax ; set to 0 using the value of eax
mov DWORD PTR 36+array[rip], eax
注意eax
为返回值归零,但也用于归零2个内存区域(最后2条指令),可能是因为使用eax
的指令短于具有立即数零的指令操作数。
所以两只一石二鸟。
答案 1 :(得分:5)
不同的指令有不同的延迟。有时,更改指令的顺序可以加速代码,原因有几个。例如: 如果某个指令需要几个周期才能完成,如果它在函数的末尾,程序就会等待它完成。如果它在函数的早期,则在该指令完成时可能发生其他事情。不过,这里的实际原因不太可能是第二个想法,因为我认为寄存器是低延迟指令。但是,延迟是依赖于处理器的。
但是,放置XOR可能与分离放置它的mov指令有关。
还有一些优化可以利用现代处理器的优化功能,例如流水线,分支预测(据我所知,这里不是这种情况......)等等。您需要对这些处理器有一个非常深刻的理解能够理解优化器可以利用它们做些什么。
您可能会发现this信息丰富。它向我指出了一个我以前从未见过的资源,但有很多你想要的信息(或者不想要:-))知道但是却不敢问:-) here
答案 2 :(得分:3)
预计这些存储器访问将至少燃烧几个时钟周期。您可以在不更改代码功能的情况下移动xor。通过一次/一些内存访问将其拉回来它就变得空闲,不花费你任何执行时间它与外部访问并行(处理器完成xor并等待外部活动而不是等待外部活动) 。如果你把它放在一堆没有内存访问的指令中,它至少要花费一个时钟。正如您可能知道使用xor vs mov立即减小指令的大小,可能不会花费时钟,但会节省二进制文件中的空间。一个非常酷的优化,可以追溯到最初的8086,并且至今仍然使用它,即使它最终没有为你节省太多。
答案 3 :(得分:-1)
在处理器设置的情况下,特定值取决于通过执行树的时刻,确保不再需要该寄存器,并且不会被外部世界更改。
这里不那么简单的例子: https://godbolt.org/g/6AowMJ
并且处理器将eax超过memset,因为memset可以更改其值。这个时刻取决于解析复杂的树,对人类来说可能不合逻辑。