为什么icc无法以合理的方式处理编译时分支提示?

Bee*_*ope 10 c optimization x86 built-in icc

开发人员可以使用__builtin_expect 内置函数来帮助编译器了解分支可能走向哪个方向.

将来,我们可能会为此目的获得一个标准属性,但截至今天至少全部clang,iccgcc支持非标准属性__builtin_expect.

但是,icc当你使用它时,似乎会生成奇怪的代码1.也就是说,无论使用哪个方向进行预测,使用内置函数的代码都严格地比没有内置代码的代码更糟糕.

以下面的玩具功能为例:

int foo(int a, int b)
{
  do {
     a *= 77;
  } while (b-- > 0);  
  return a * 77;
}
Run Code Online (Sandbox Code Playgroud)

在三个编译器中,icc唯一一个将其编译为3个指令的最佳标量循环:

foo(int, int):
..B1.2:                         # Preds ..B1.2 ..B1.1
        imul      edi, edi, 77                                  #4.6
        dec       esi                                           #5.12
        jns       ..B1.2        # Prob 82%                      #5.18
        imul      eax, edi, 77                                  #6.14
        ret          
Run Code Online (Sandbox Code Playgroud)

无论GCCClang的管理无缘简单的解决方案,并使用5条指令.

另一方面,当你在循环条件下使用likelyunlikely宏时,icc完全是脑死亡:

#define likely(x)   __builtin_expect((x), 1)
#define unlikely(x) __builtin_expect((x), 0)

int foo(int a, int b)
{

   do {
     a *= 77;
  } while (likely(b-- > 0));  

   return a * 77;
}
Run Code Online (Sandbox Code Playgroud)

这个循环在功能上等同于前一个循环(因为__builtin_expect只返回它的第一个参数),但icc会产生一些可怕的代码:

foo(int, int):
        mov       eax, 1                                        #9.12
..B1.2:                         # Preds ..B1.2 ..B1.1
        xor       edx, edx                                      #9.12
        test      esi, esi                                      #9.12
        cmovg     edx, eax                                      #9.12
        dec       esi                                           #9.12
        imul      edi, edi, 77                                  #8.6
        test      edx, edx                                      #9.12
        jne       ..B1.2        # Prob 95%                      #9.12
        imul      eax, edi, 77                                  #11.15
        ret                                                     #11.15
Run Code Online (Sandbox Code Playgroud)

该函数的大小增加了一倍,达到10条指令,并且(更糟糕的是!)关键循环的数量增加了一倍多,达到7条指令,其中一条长关键依赖链涉及一个cmov奇怪的东西.

如果您使用unlikely提示,并且也使用Godbolt支持的所有icc版本(13,14,17),情况也是如此.因此,无论提示如何,代码生成都严格地更糟,并且无论实际的运行时行为如何.

使用提示时,既gcc不会clang降低也不会有任何降级.

那是怎么回事?


1至少在我试过的第一个和后续的例子中.

Mar*_*oom 3

对我来说这似乎是一个 ICC 错误。此代码(可在 godbolt 上找到

int c;

do 
{
    a *= 77;
    c = b--;
} 
while (likely(c > 0));  
Run Code Online (Sandbox Code Playgroud)

仅使用辅助局部 var ,生成不带模式的c输出edx = !!(esi > 0)

foo(int, int):
  ..B1.2:                         
    mov       eax, esi
    dec       esi
    imul      edi, edi, 77
    test      eax, eax
    jg        ..B1.2
Run Code Online (Sandbox Code Playgroud)

不过,仍然不是最佳的(没有它也可以eax)。

我不知道ICC官方的政策__builtin_expect是完全支持还是只是兼容性支持


这个问题似乎更适合ICC官方论坛
我已经尝试在那里发布这个主题,但我不确定我是否做得很好(我已经被这样宠坏了)。
如果他们回答我,我会更新这个答案。

编辑
我在英特尔论坛上得到了答案,他们在跟踪系统中记录了这个问题。
就像今天一样,这似乎是一个错误。