虽然(i--)通过gcc和clang进行优化:为什么他们不使用sub/jnc?

l4m*_*4m2 9 c performance x86 assembly gcc

有些人在需要没有计数器或计数器的循环时编写这样的代码n-1, ..., 0:

while (i--) { ... }
Run Code Online (Sandbox Code Playgroud)

一个具体的例子:

volatile int sink;
void countdown_i_used() {
    unsigned i = 1000;
    while (i--) {
         sink = i;  // if i is unused, gcc optimizes it away and uses dec/jnz
    }
}
Run Code Online (Sandbox Code Playgroud)

在GCC 8.2(在Godbolt编译器资源管理器上),它被编译成

# gcc8.2 -O3 -march=haswell
.L2:
    mov     DWORD PTR sink[rip], eax
    dec     eax                      # with tune=generic,  sub eax, 1
    cmp     eax, -1
    jne     .L2
Run Code Online (Sandbox Code Playgroud)

在clang(https://godbolt.org/z/YxYZ95)上,如果不使用计数器,它会变成

if(i) do {...} while(--i);
Run Code Online (Sandbox Code Playgroud)

但如果使用,就像GCC一样

add esi, -1
cmp esi, -1
jnz lp
Run Code Online (Sandbox Code Playgroud)

但是,这似乎是一个更好的主意:

sub esi, 1
jnc lp
Run Code Online (Sandbox Code Playgroud)

为什么这两个编译器不会这样使用?

因为cmp方式更好?或者因为它们不会以这种方式节省空间而且速度几乎相同?

或者他们只是不考虑这个选项?

Pet*_*des 7

是的,这是一个错过的优化.Intel Sandybridge系列可以将sub/jcc宏熔合到单个uop中,因此sub/jnc可以在这些CPU上保存代码大小,x86指令和uop.

在其他CPU上(例如AMD只能将测试/ cmp与jcc融合在一起),这仍然可以节省代码大小,因此它至少会稍好一些.任何事情都不会更糟.

https://bugs.llvm.orghttps://gcc.gnu.org/bugzilla/上报告错过优化错误是个好主意.

  • @l4m2:您的更新中没有文字“jc”,您只是有一个编译器*可以*识别为进位检查的习惯用法。但是,通过恒定的行程计数,编译器可以看到循环何时结束,并以它显然喜欢的次优方式进行编译。如果您希望编译器开发人员更改 gcc 和 LLVM 以免搞砸,请报告错过优化的错误。 (2认同)