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

Azs*_*sgy 8 c optimization assembly gcc compilation

我在最近关于array[i++]vs 的假设速度的争论中写了这个片段array[i]; i++.

int array[10];

int main(){
    int i=0;
    while(i < 10){
        array[i] = 0;
        i++;
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译器资源管理器的代码段:https://godbolt.org/g/de7TY2

正如所预期的,编译器输出相同ASM array[i++]array[i]; i++与至少-O1.然而令我感到惊讶的是,xor eax, eax在更高的优化级别中,看似随机地放置在函数中.


GCC

-O2,GCC会将xor在之前ret预期

    mov     DWORD PTR [rax], 0
    add     rax, 4
    cmp     rax, OFFSET FLAT:array+40
    jne     .L2
    xor     eax, eax
    ret
Run Code Online (Sandbox Code Playgroud)

然而,它在第二个之后将xor置于mov-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
Run Code Online (Sandbox Code Playgroud)

ICC

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
Run Code Online (Sandbox Code Playgroud)

但是在一个陌生的地方 -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       
Run Code Online (Sandbox Code Playgroud)

和-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   
Run Code Online (Sandbox Code Playgroud)

只有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
Run Code Online (Sandbox Code Playgroud)

由于GCC和ICC都在更高的优化级别上执行此操作,因此我认为必定有某种充分的理由.

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

代码在语义上是相同的,编译器可以按照自己的意愿重新排序,但由于这只会在更高的优化级别进行更改,因此必须由某种优化引起.

Jea*_*bre 6

由于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
Run Code Online (Sandbox Code Playgroud)

注意eax返回值为零,但也用于归零2个存储区(最后2条指令),这可能是因为使用的指令eax短于具有立即零操作数的指令.

所以两只一石二鸟.

  • 不确定而不是专家,但也许在相同的指令序列中间这样做可以更好地利用指令流水线(因为它们不处理相同的数据,寄存器与内存). (3认同)

Bas*_*sya 5

不同的指令具有不同的延迟.有时,更改指令的顺序可以加速代码,原因有几个.例如:如果某个指令需要几个周期才能完成,如果它在函数末尾,程序就会等待它完成.如果它在函数的早期,则在该指令完成时可能发生其他事情.不过,这里的实际原因不太可能是第二个想法,因为我认为寄存器是低延迟指令.但是,延迟是依赖于处理器的.

但是,放置XOR可能与分离放置它的mov指令有关.

还有一些优化可以利用现代处理器的优化功能,例如流水线,分支预测(在我看来就不是这种情况......)等等.您需要对这些功能有深入的了解才能理解优化器可以做些什么来利用它们.

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

  • 然而,在OoOE时代,延迟调度并不是非常有用.并且自我xor甚至没有明确定义的延迟,因为它依赖于任何东西. (2认同)