【问题标题】:Branch on ?: operator??: 运算符上的分支?
【发布时间】:2011-08-19 21:45:05
【问题描述】:

对于现代硬件上的典型现代编译器,? : 运算符会导致影响指令管道的分支吗?

换句话说更快,调用这两种情况以避免可能的分支:

bool testVar = someValue(); // Used later.
purge(white);
purge(black);

或者选择一个真正需要被清除的并且只使用操作员?:

bool testVar = someValue();
purge(testVar ? white : black);

我知道您不知道 purge() 需要多长时间,但我只是在这里问一个一般性问题,即我是否想调用 purge() 两次以避免代码中可能出现的分支。

我意识到这是一个非常小的优化,可能没有真正的区别,但仍然想知道。我希望?: 不会导致分支,但想确保我的理解是正确的。

【问题讨论】:

  • 我很确定函数调用会比分支更昂贵,但我知道的不足以支持它并将其作为答案发布。你为什么不同时尝试和配置文件?
  • 仅供参考,?: 被称为 三元 运算符。
  • 你为什么不用你的编译器试试看呢?看看生成的汇编代码就行了。
  • @Jared Ng - 不完全是。三元运算符是具有三个操作数的任何运算符。大多数语言只有一个三元运算符 - ?: - 但有些(某处)有其他运算符。三元类似于二元或一元。 ?:a 三元运算符,但它的名字是 conditional 运算符。
  • @GMan - 我假设(因为 OP 似乎暗示任何一个选项都是可以接受的)purgeing 可能不需要的东西是 nop(也许不在程序集中,但实际上)。

标签: c++ compiler-construction hardware micro-optimization


【解决方案1】:

取决于平台。具体取决于CPU的跳转预测表大小以及CPU是否允许条件操作(如ARM)。

具有条件操作的 CPU 将强烈支持第二种情况。具有较大跳跃预测表的 CPU 将支持第一种情况。

真正的答案(与任何其他性能问题一样):衡量和比较。有时代码的其余部分会抛出一个曲线球,通常无法预测某些更改的影响。

【讨论】:

  • 大多数现代 ISA 都有某种形式的条件选择,例如 x86-64 cmov、AArch64 csel、MIPS movz。对于两个可供选择的选项不需要任何额外计算的情况,大多数编译器将编译一个简单的三元组。 (在某些 ISA 上,例如 32 位 ARM 和我认为是 Itanium,实际上可以避免在没有分支的情况下不需要其值的缓存未命中负载上停止,这使得无分支更好。在 ARM 上通过实际条件加载,或者通过推测加载在 Itanium 上,我认为。)
  • 即使没有像 cmov 这样的指令,bithack 也可以将布尔值转换为 0 / 0xffffffff 掩码,可以用于和/或实现相同的操作。在一些 CPU 上,编译器甚至可能会选择这样做,尤其是对于像 2 的补码 abs() 这样的操作,其中无分支的 bithack 只是为了这几条指令。
【解决方案2】:

自 Pentium Pro 以来,CMOV(条件 MOVe)指令已成为 x86 指令集的一部分。由于常用的编译器选项和 C 语言的限制,它很少由 GCC 自动生成。 SETCC/CMOV 序列可以通过内联汇编插入到您的 C 程序中。只有在条件变量是程序内部循环(数百万次执行)中的随机振荡值的情况下才应该这样做。在非振荡情况和简单振荡模式的情况下,现代处理器可以非常准确地预测分支。 2007 年,Linus Torvalds suggested here 避免在大多数情况下使用 CMOV。

英特尔在Intel(R) Architecture Software Developer's Manual, Volume 2: Instruction Set Reference Manual 中描述了条件移动:

CMOVcc 指令检查一个或多个状态的状态 EFLAGS 寄存器(CF、OF、PF、SF 和 ZF)中的标志并执行 如果标志处于指定状态(或条件),则移动操作。一个 条件代码 (cc) 与每条指令相关联,以指示 被测试的条件。如果条件不满足,则 不执行移动,继续执行指令 遵循 CMOVcc 指令。

这些指令可以将 16 位或 32 位值从内存移动到 通用寄存器或从一个通用寄存器到 其他。 8 位寄存器操作数的条件移动不是 支持。

每个 CMOVcc 助记符的条件在描述中给出 上表的列。术语“更少”和“更大”用于 有符号整数的比较和术语“上”和“下”是 用于无符号整数。

因为状态标志的特定状态有时可以 以两种方式解释,为某些操作码定义了两个助记符。 例如,CMOVA(如上条件移动)指令和 CMOVNBE(不低于或等于条件移动)指令是 操作码 0F 47H 的替代助记符。

【讨论】:

    【解决方案3】:

    我无法想象第一种方法会更快。

    使用第一种方法,您可以避免分支,但您可以将其替换为函数调用,这通常会涉及分支以及更多内容(除非它是内联的)。即使是内联的,除非 purge() 函数内部的功能绝对是微不足道的,否则它几乎肯定会更慢。

    【讨论】:

    • 不一定。该无条件分支不需要刷新管道。如果功能比较简单,整体可能会比较便宜。
    【解决方案4】:

    调用函数至少与执行逻辑测试 + 跳转一样昂贵(是的,? : 三元运算符需要跳转)。

    【讨论】:

    • 假设这一切都在循环中频繁发生,无条件函数调用不会导致分支错误预测,因此不需要刷新 CPU 管道。逻辑测试可能涉及条件跳转;这将比无条件跳转昂贵得多。
    【解决方案5】:

    在第一种情况下,purge 被调用两次。在第二种情况下,purge 被调用一次

    很难回答有关分支的问题,因为它非常依赖于编译器和指令集。例如,在 ARM(具有条件指令执行)上,它可能不会分支。在 x86 上它几乎肯定会

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-02-13
      • 2012-08-15
      • 2017-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-26
      • 2018-12-17
      相关资源
      最近更新 更多