为什么编译器似乎更喜欢将pretest循环优化为检查,条件跳转,然后是do-while结构,而不是在do while循环中进行无条件跳转?
我编写了一个用我描述的第二种风格编写的函数,但是g ++和clang都将其撤消并将其转换为方法一。 https://godbolt.org/g/2Dvudi
我很困惑,因为看起来编译器复制了许多指令(对于这个例子来说可能没那么多)用于预测试。此外,无论如何它可能会跳跃(虽然可能是静态预测不会被采取,并且在一般情况下并不是很重要),所以为什么不总是无条件地跳转?
以下是我对此的一个想法,但它并不强烈支持这两种方法:
循环想要对齐,所以可能有空间尽可能地预先复制一些指令而不浪费空间,因为它们将用nops填充。但是,clang和gcc都会为预测试发出超过16个字节的代码,最后会插入一个大的nop。
编辑:以下是godbolt链接的代码:
typedef unsigned char uchar;
unsigned my_atoi(const uchar *p)//sentinel at end
{
unsigned acm=0u;
unsigned d;
goto LEnter;
do{
acm = acm*10u + d;
LEnter:
d = *p++ - '0';
}while (d<10u);
return acm;
}
clang 5.0 at -O2发出:
my_atoi(unsigned char const*): # @my_atoi(unsigned char const*)
movzx ecx, byte ptr [rdi]
add ecx, -48
xor eax, eax
cmp ecx, 9
ja .LBB0_3
inc rdi
xor eax, eax
.LBB0_2: # =>This Inner Loop Header: Depth=1
lea eax, [rax + 4*rax]
lea eax, [rcx + 2*rax]
movzx ecx, byte ptr [rdi]
add ecx, -48
inc rdi
cmp ecx, 10
jb .LBB0_2
.LBB0_3:
ret
答案 0 :(得分:1)
引用GCC sources相关优化过程中的一些评论。
如果循环的标题足够小,则复制循环的标题,以便声明 在循环体中总是在输入循环时执行。这个 提高代码运动优化的有效性,并减少需求 for loop preconditioning。
即,如果后来的传递找到一些循环不变的代码,他们将有一个位置来移动该代码而无需添加检查,无论循环是否会迭代。
对于所有循环,复制前面循环体末端的条件 循环。这是有益的,因为它提高了效率 代码运动优化。它还可以在进入循环时保存一次跳转。
答案 1 :(得分:0)
正在初始化d
并移除goto
会让它不再奇怪。
typedef unsigned char uchar;
unsigned my_atoi(const uchar *p) {//sentinel at end
unsigned acm=0u;
unsigned d=0; // initialized
// goto LEnter;
do{
acm = acm*10u + d;
// LEnter:
d = *p++ - '0';
}while (d<10u);
return acm;
}
xor eax,eax // acm=0
xor ecx,ecx // d=0
data16 data16 nop WORD PTR cs:[rax+rax*1+0x0] // nops, aligns to 16 bytes
lea eax,[rax+rax*4] // *5
lea eax,[rcx+rax*2] // *2+d
movzx ecx,BYTE PTR [rdi] // d=*p
inc rdi // p++
add ecx,0xffffffd0 // d-'0'
cmp ecx,0xa // d<10
jb 4004e0 <my_atoi(unsigned char const*)+0x10>
ret
data16 nop WORD PTR cs:[rax+rax*1+0x0]
main:
xor eax,eax
ret
nop WORD PTR cs:[rax+rax*1+0x0]
nop DWORD PTR [rax]
因此,编译器加倍检查的原因是您将跳转到非对齐代码。中央循环看起来很小,可以在CPU的循环缓冲区中,但跳到它的中间可能会污染循环缓冲区。