【问题标题】:Is there a way to convert a conditional assignment to branch free code?有没有办法将条件赋值转换为无分支代码?
【发布时间】:2017-09-08 23:52:15
【问题描述】:

有没有办法将下面的 C 代码转换为不带任何条件语句的东西?我已经分析了我的一些代码,并注意到它在与此非常相似的 if 语句上出现了许多分支未命中。

int cond = /*...*/;
int a = /*...*/;
int b = /*...*/;

int x;
if (cond) {
   x = a;
} else {
   x = b;
}

【问题讨论】:

  • 你在这个片段后面使用ab吗?我假设你在这之后也使用x
  • @JonathanLeffler:不,我不在这个片段之后使用 a 和 b。
  • 如果你在这个片段后面不使用ab,那么你不需要同时计算这两个,除非它们的计算有副作用。
  • 可能编译器认为最好避免计算不需要的ab,即使以分支预测错误为代价。谁知道呢?也许是对的。
  • 以上假设您告诉编译器要编译哪个架构,该架构具有条件移动操作码,并且您已请求优化。如果所有这些都是真的,编译器应该发出条件移动指令,除非它有充分的理由不这样做。

标签: c branch-prediction


【解决方案1】:

这取决于您所针对的指令集。对于 x86,有 cmov。对于 arm64,有 csel。对于 armv7,mov 带有一个可选的条件操作码。

任何体面的编译器都应该能够将您拥有的代码优化为最优化的指令集。 GCC 和 clang 会这样做(在 https://gcc.godbolt.org/ 自己尝试一下)。

更直接地回答您的问题:没有办法在直接 C 中强制执行此操作,因为 CPU 指令集可能没有可用作替代的无分支指令。所以你要么必须依赖你的编译器(这可能是一个好主意),要么你自己编写程序集。

举个小例子,考虑下面的 C 代码:

int min(int a, int b) {
  int result;
  if (a < b) {
    result = a;
  } else {
    result = b;
  }
  return result;
}

gcc 5.4.1 for armv7 生成:

min(int, int):
        cmp     r0, r1
        movge   r0, r1
        bx      lr

gcc 5.4 for arm64 生成:

min(int, int):
        cmp     w0, w1
        csel    w0, w0, w1, le
        ret

clang 4.0 for x86 生成:

min(int, int):                               # @min(int, int)
        cmp     edi, esi
        cmovle  esi, edi
        mov     eax, esi
        ret

gcc 5 for x86 生成:

min(int, int):
        cmp     edi, esi
        mov     eax, esi
        cmovle  eax, edi
        ret

icc 17 for x86 生成:

min(int, int):
        cmp       edi, esi                                      #8.10
        cmovl     esi, edi                                      #8.10
        mov       eax, esi                                      #8.10
        ret                                                     #8.10

如您所见,它们全部无分支(在-O1 或更高版本编译时)。

【讨论】:

  • 谢谢。问题一定是我的原始代码有一些额外的东西让编译器感到困惑......我想我必须更仔细地看看它。
【解决方案2】:

更完整的示例会更有帮助,因为变量xabcond 的访问方式可以发挥作用。如果它们是在执行条件赋值的函数之外声明的全局变量,那么将使用加载和存储来访问它们,编译器可能认为它们太昂贵而无法有条件地执行。 查看https://godbolt.org/g/GEZbuf 中的示例,其中对全局变量和foo 以及foo2 中的局部参数执行相同的条件赋值

【讨论】:

    【解决方案3】:
    x = (!cond * b) | (!!cond * a);
    

    【讨论】:

      猜你喜欢
      • 2022-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多