【问题标题】:likely/unlikely equivalent for MSVCMSVC 可能/不太可能等效
【发布时间】:2010-11-29 06:15:15
【问题描述】:

GCC 编译器支持 __builtin_expect 语句,用于定义可能和不可能的宏。

例如。

#define likely(expr)    (__builtin_expect(!!(expr), 1))
#define unlikely(expr)  (__builtin_expect(!!(expr), 0))

是否有 Microsoft Visual C 编译器的等效语句或等效语句?

【问题讨论】:

标签: visual-studio gcc optimization compiler-construction likely-unlikely


【解决方案1】:

__assume 应该类似。

但是,如果您想很好地做到这一点,您应该使用Profile Guided Optimization 而不是静态提示。

【讨论】:

  • 我认为这可能很危险。根据微软的说法:“由于编译器基于 __assume 生成代码,如果 __assume 语句中的表达式在运行时为 false,则该代码可能无法正确运行。”
  • @Digital - 非常正确,链接到 MSDN 的文章描述了陷阱。同样,应避免此类静态提示,并且应尽可能使用 PGO。
  • 抱歉,PGO 是 PITA,尤其是在任何相当复杂的库中。我更清楚自己的代码中什么是可能的,什么是不可能的。
【解决方案2】:

我说只是平底船

没有什么比这更好的了。有__assume(),但不要使用它,它是另一种优化器指令。

真的,gnu 内置函数被封装在宏中的原因是,如果未定义 __GNUC__,您可以自动摆脱它。这些宏没有任何必要,我敢打赌你不会注意到运行时间差异。

总结

在非 GNU 上去掉(null out)*likely。你不会错过的。

【讨论】:

  • 我有一个硬件设备,在调用函数后,我需要像safeCall(mDevice.doit()) 这样进行检查,我的safeCall 是内联的,这可以提高性能,但只有当我有可能/不太可能的分支时.我想说这些宏很有用。
  • “一般来说,您应该更喜欢使用实际的配置文件反馈来进行 [分支预测] (-fprofile-arcs),因为众所周知,程序员不擅长预测他们的程序实际执行情况。” gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html
  • 我确实想念unlikely 内在函数。如果没有 PGO(这是一个 PITA),愚蠢的 MSVC几乎总是以错误的方式命令指令。
  • 尽管您确实建议不要这样做,但在这种情况下将_assume 命名为“关闭”有点不吉利,因为它甚至不是很相似。不知情的读者可能会错误地解释“可能得到不正确的代码”。其含义相当于__builtin_unreachable。将它用于分支提示不仅危险,而且总是不正确
  • 更准确地说,_assume(cond) 在语义上很大程度上等同于 GCC 中的 if (!(cond)) __builtin_unreachable();。这意味着将其用作分支提示允许编译器删除整个“无法访问”的分支和条件,如果它没有副作用的话。
【解决方案3】:

根据英特尔的Branch and Loop Reorganization to Prevent Mispredicts 文档:

为了有效地编写代码以利用这些 规则,在编写 if-else 或 switch 语句时,检查最 首先是常见情况,然后逐步处理到最不常见的情况。

不幸的是你不能写类似的东西

#define if_unlikely(cond) if (!(cond)); else 

因为从 VS10 开始的 MSVC 优化器会忽略此类“提示”。

由于我更喜欢​​在代码中首先处理错误,因此我编写的代码似乎效率较低。 幸运的是,CPU 第二次遇到分支时,它将使用其统计信息而不是静态提示。

【讨论】:

  • 对 Xentrax 的回答:您关于 MSVC 的陈述似乎与我的观察不符。我正在使用 VS 2010 并看到编译器在使用普通 if 时生成“jne”。但是当使用 else 代替时,编译器会生成一个“je”,并将 else 块放在主要流程之后。因此,使用 MSVC,您的定义似乎确实有效。现在,如果我能找到 MS 的声明,表明这是有意的,受支持的行为......
  • @Ruben:至少从 MSVC 2005 开始,我就一直在利用这种代码生成技巧。从那以后,它在所有版本中仍然如此。
【解决方案4】:

根据http://www.akkadia.org/drepper/cpumemory.pdf(第57页),即使CPU动态预测正确,使用静态分支预测仍然有意义。 原因是如果静态预测正确,L1i 缓存的使用效率会更高。

【讨论】:

    【解决方案5】:

    C++20 标准将包括[[likely]][[unlikely]] 分支预测属性。

    属性提案的最新版本可以在http://wg21.link/p0479找到

    原始属性proposal可以从http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0479r0.html找到

    程序员应该更喜欢 PGO。如果应用不正确,属性很容易降低性能,或者在程序更改时它们会变得不正确。

    【讨论】:

    【解决方案6】:

    我知道这个问题是关于 Visual Studio 的,但我会尽量回答尽可能多的编译器(包括 Visual Studio)……

    十年后有进步!截至 Visual Studio 2019 MSVC 仍然不支持这样的东西(即使它是 the most popular builtin/intrinsic),但正如 Pauli Nieminen 上面提到的 C++20 有 likely / unlikely attributes 可用于创建可能/不太可能的宏和 MSVC 通常很快就添加了对新 C++ 标准的支持(与 C 不同),因此我希望 Visual Studio 2021 能够支持它们。

    目前(2019-10-14)只有 GCC 支持这些属性,即使那时也只应用于标签,但至少做一些基本的测试就足够了。这是一个快速实现,您可以test on Compiler Explorer

    #define LIKELY(expr) \
      ( \
        ([](bool value){ \
          switch (value) { \
            [[likely]] case true: \
              return true; \
            [[unlikely]] case false: \
              return false; \
          } \
        }) \
      (expr))
    #define UNLIKELY(expr) \
      ( \
        ([](bool value){ \
          switch (value) { \
            [[unlikely]] case true: \
              return true; \
            [[likely]] case false: \
              return false; \
          } \
        }) \
      (expr))
    

    您可能希望围绕它使用#ifdef 来支持无法处理它的编译器,但幸运的是大多数编译器都支持__builtin_expect

    • GCC 3.0
    • 叮当
    • ICC 至少从 13 年开始,可能更长。
    • Oracle Development Studio 12.6+,但仅限于 C++ 模式。
    • ARM 4.1
    • IBM XL C/C++ 至少从 10.1 开始,可能更长。
    • TI 自 6.1 起
    • TinyCC 从 0.9.27 开始

    GCC 9+ 也支持__builtin_expect_with_probability。它在其他任何地方都不可用,但希望有朝一日......试图弄清楚是否使用 ilkely/unlikely 需要大量的猜测 - 你只需设置概率并且编译器(理论上)会做正确的事情。

    此外,clang 支持 __builtin_unpredictable(从 3.8 开始,但使用 __has_builtin(__builtin_unpredictable) 对其进行测试)。由于现在很多编译器都是基于 clang 的,所以它可能也适用于它们。

    如果您希望这一切都结束并准备就绪,您可能会对我的一个项目感兴趣,Hedley。它是一个单一的公共域 C/C++ 头文件,几乎适用于所有编译器,并包含许多有用的宏,包括 HEDLEY_LIKELYHEDLEY_UNLIKELYHEDLEY_UNPREDICTABLEHEDLEY_PREDICTHEDLEY_PREDICT_TRUEHEDLEY_PREDICT_FALSE。它还没有 C++20 版本,但是it should be there soon...

    即使您不想在项目中使用 Hedley,您也可能希望检查那里的实现,而不是依赖上面的列表;我可能会忘记用新信息更新这个答案,但 Hedley 应该始终是最新的。

    【讨论】:

      【解决方案7】:

      现在 MS said 他们已经实现了likely/unlikely attributes

      但实际上使用“可能”和不使用并没有什么区别。

      我已经编译了这些代码并且生成了相同的result

          int main()
          {
              int i = rand() % 2;
              if (i) [[likely]]
              {
                 printf("Hello World!\n");
              }
              else
              {
                  printf("Hello World2%d!\n",i);
              }
          }
      
          int main()
          {
              int i = rand() % 2;
              if (i)
              {
                 printf("Hello World!\n");
              }
              else [[likely]]
              {
                  printf("Hello World2%d!\n",i);
              }
          }
      
      int pdb._main (int argc, char **argv, char **envp);
      0x00401040      push    ebp
      0x00401041      mov     ebp, esp
      0x00401043      push    ecx
      0x00401044      call    dword [rand] ; pdb.__imp__rand
                                         ; 0x4020c4
      0x0040104a      and     eax, 0x80000001
      0x0040104f      jns     0x401058
      0x00401051      dec     eax
      0x00401052      or      eax, 0xfffffffe ; 4294967294
      0x00401055      add     eax, 1
      0x00401058      je      0x40106d
      0x0040105a      push    str.Hello_World ; pdb.___C__0O_NFOCKKMG_Hello_5World__CB_6
                                         ; 0x402108 ; const char *format
      0x0040105f      call    pdb._printf ; int printf(const char *format)
      0x00401064      add     esp, 4
      0x00401067      xor     eax, eax
      0x00401069      mov     esp, ebp
      0x0040106b      pop     ebp
      0x0040106c      ret
      0x0040106d      push    0
      0x0040106f      push    str.Hello_World2_d ; pdb.___C__0BB_DODJFBPJ_Hello_5World2__CFd__CB_6
                                         ; 0x402118 ; const char *format
      0x00401074      call    pdb._printf ; int printf(const char *format)
      0x00401079      add     esp, 8
      0x0040107c      xor     eax, eax
      0x0040107e      mov     esp, ebp
      0x00401080      pop     ebp
      0x00401081      ret
      

      【讨论】:

      【解决方案8】:

      由于问题很老,所以说 MSVC 中没有 [[likely]] / [[unlikely]] 或没有影响的答案已经过时了。

      最新的 MSVC 在/std:c++20/std:c++latest 模式下支持[[likely]] / [[unlikely]]

      请参阅 demo on Godbolt's compiler explorer 以显示差异。

      从上面的链接可以看出,if-else 语句对 x86/x64 的一个明显影响是条件跳转将用于不太可能的分支。在 C++20 和支持 VS 版本之前,可以通过将可能的分支放入 if 部分,将不太可能的分支放入 else 部分来实现相同的目的,根据需要否定条件。

      请注意,这种优化的效果是微乎其微的。对于紧密循环中频繁调用的代码,动态分支预测无论如何都会做正确的事情。

      【讨论】:

        猜你喜欢
        • 2021-12-19
        • 1970-01-01
        • 1970-01-01
        • 2012-06-10
        • 2014-02-17
        • 1970-01-01
        • 2016-04-13
        • 2021-05-28
        • 1970-01-01
        相关资源
        最近更新 更多