【发布时间】:2019-06-14 03:22:45
【问题描述】:
有些人在需要不带计数器或带n-1, ..., 0 计数器的循环时编写此类代码:
while (i--) { ... }
一个具体的例子:
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
}
}
在 GCC 8.2 (on the Godbolt compiler explorer) 上,它被编译成
# 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
在 clang (https://godbolt.org/z/YxYZ95) 上,如果不使用计数器,它会变成
if(i) do {...} while(--i);
但如果使用的话,像 GCC 一样会变成
add esi, -1
cmp esi, -1
jnz lp
不过,这似乎是一个更好的主意:
sub esi, 1
jnc lp
这两个编译器为什么不用这种方式?
因为cmp 方式更好?还是因为它们不会以这种方式节省空间而且它们的速度几乎相同?
或者他们只是不考虑这个选项?
更新:即使我写代码使用进位方式(这里我使用add/jc但它是一样的)
bool addcy(unsigned& a, unsigned b) {
unsigned last_a = a;
a+=b;
return last_a+b<last_a;
}
volatile unsigned sink;
void f() {
for (unsigned i=100; addcy(i, -1); ) {
sink = i;
}
}
compiler still compile it as checking equality to -1。但是,如果将 100 替换为输入,the JC code remain
【问题讨论】:
-
发布minimal reproducible example。还要确保您启用了优化。
-
gcc 在将循环优化到一个宏融合的 uop 开销方面出奇地糟糕。使用
for(int i=0 ; i < size ; i++)循环,即使i在循环内未使用,您也倾向于得到add/cmp/jcc而不是dec size/jnz。但是是的sub/jnc可能是最佳的;可以在 Sandybridge-family 上进行宏融合。 -
看起来你在循环中使用
i,否则 gcc 会更好。
标签: c performance gcc assembly x86