为什么clang和gcc在循环内重复代码和分支与无条件跳转?

时间:2017-10-13 20:02:19

标签: c++ c optimization x86

为什么编译器似乎更喜欢将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

2 个答案:

答案 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的循环缓冲区中,但跳到它的中间可能会污染循环缓冲区。