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方式更好?或者因为它们不会以这种方式节省空间而且速度几乎相同?
或者他们只是不考虑这个选项?
是的,这是一个错过的优化.Intel Sandybridge系列可以将sub/jcc宏熔合到单个uop中,因此sub/jnc可以在这些CPU上保存代码大小,x86指令和uop.
在其他CPU上(例如AMD只能将测试/ cmp与jcc融合在一起),这仍然可以节省代码大小,因此它至少会稍好一些.任何事情都不会更糟.
在https://bugs.llvm.org和https://gcc.gnu.org/bugzilla/上报告错过优化错误是个好主意.