为什么有些C编译器会在奇怪的地方设置函数的返回值?

为什么有些C编译器会在奇怪的地方设置函数的返回值?,c,gcc,assembly,optimization,compilation,C,Gcc,Assembly,Optimization,Compilation,我在最近一次关于array[I++]vsarray[I]的假定速度的争论中写下了这个片段;i++ int array[10]; int main(){ int i=0; while(i < 10){ array[i] = 0; i++; } return 0; } 但是,它将xor放在第二个movat-O3之后 mov QWORD PTR array[rip], 0 mov QWORD PT

我在最近一次关于
array[I++]
vs
array[I]的假定速度的争论中写下了这个片段;i++

int array[10];

int main(){
    int i=0;
    while(i < 10){
        array[i] = 0;
        i++;
    }
    return 0;
}
但是,它将xor放在第二个
mov
at-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   

叮当声 在所有优化级别上,只有叮当声将
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都是在更高的优化级别上这样做的,我认为一定有某种很好的理由

为什么有些编译器会这样做


当然,代码在语义上是相同的,编译器可以根据需要对其重新排序,但由于这只会在更高的优化级别上发生变化,因此这一定是由某种优化引起的。

处理器设置特定值的位置取决于通过执行树的时刻,因此可以确定,该寄存器不再需要,也不会被外部世界更改

下面是一个不那么琐碎的例子:


处理器通过memset将eax归零,因为memset可以更改其值。这一时刻取决于对复杂树的解析,对人类来说可能不符合逻辑。

由于未使用
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
的指令比使用立即归零操作数的指令短


所以一石二鸟。

不同的指令有不同的延迟。有时,由于几个原因,更改指令顺序可以加快代码的速度。例如: 如果某条指令需要几个周期才能完成,如果它在函数末尾,程序只需等待它完成。如果它在函数中的较早位置,则在该指令完成时可能会发生其他事情。这不太可能是这里的实际原因,不过,仔细想想,因为寄存器的异或(xor)是我认为的低延迟指令。不过,延迟取决于处理器

但是,将XOR放置在其中可能与分离放置它的mov指令有关

还有一些优化利用了现代处理器的优化功能,如流水线、分支预测(就我所见,这里的情况并非如此……)等。您需要对这些功能有相当深入的了解,才能理解优化器可以如何利用它们


你可能会发现信息丰富。它向我指出了一个我以前从未见过的资源,但它有很多您想要(或不想要:-)知道但又不敢问的信息:-)

这些内存访问预计会消耗至少几个时钟周期。您可以在不更改代码功能的情况下移动xor。通过在其空闲后通过一次/一些内存访问将其拉回来,不会花费任何执行时间,它与外部访问并行(处理器完成xor并等待外部活动,而不仅仅是等待外部活动)。如果你把它放在一堆没有内存访问的指令中,它至少要花费一个时钟。您可能知道,使用xor vs mov immediate可以减少指令的大小,可能不需要花费时钟,但可以节省二进制文件中的空间。ghee whiz是一款很酷的优化,可以追溯到最初的8086,即使它最终并没有为您节省太多钱,它今天仍然在使用。

Like。。为什么不呢?如果代码在语义上是正确的,那么只要代码是最优的,编译器就可以生成它所希望的奇怪的代码。可读性不是一个要求。好吧,如果它输出奇怪的代码,一定是有人写了它来做到这一点,大概是出于一个很好的理由。如果没有充分的理由,为什么要让你的生活更艰难,改变指令的顺序呢?有些算法的输出可能会说“place
xor ax,ax
在X行和Y行之间的某个地方”。然后,另一个算法将把这个输出放在
X
之后,因为它从X到Y遍历行。另一个算法将把它放在Y之前,因为它在相反方向遍历它。(当然是非常抽象和简化的…)这可能与处理器的流水线有关。仔细阅读指令调度。你似乎误解了这个问题。我并不是在问为什么它要展开循环,我问为什么它要在函数中间设置函数的返回值。AppEnter回答谢谢,我没有看到这个,它肯定解释了为什么ICC早就设置EAX登记器,所以它可以在以后加载0。我很好奇为什么GCC会这么做。不确定,不是专家,但也许在同一指令序列的中间这样做可以更好地使用指令流水线(因为它们不工作在相同的数据上,寄存器和内存)。。总体思路实际上似乎正是本例的要点:
xor
指令与内存访问混合在一起,内存访问使用不同的CPU资源,并且具有较高的延迟。因此,在这些指令的中间放置<代码> XOR <代码>保证它以零开销执行。我喜欢你解释的方式,也许比我更清楚一些。但是延迟的调度在OooE时代不是非常有用的。自xor甚至没有定义好的延迟,因为它不依赖任何东西。@harold我同意给定代码中可能没有性能差异。然而,当涉及到处理更多的代码时,产生良好混合输出的编译器是
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