【问题标题】:Branchless way to conditionally clear register有条件地清除寄存器的无分支方式
【发布时间】:2018-07-21 06:48:31
【问题描述】:

是否有根据状态寄存器状态清除 32 位寄存器的无分支方式?可以使用额外的清除寄存器和CMOVcc 来实现,但对我来说,在 32 位模式下的 x86 上太贵了。可悲的是CMOVcc 没有立即操作数的版本。从内存中读取也是不好的变体。

x86 上有 SETcc(虽然,操作数是 1 个字节)但没有“CLEARcc”指令。

【问题讨论】:

  • 贵怎么办?因为注册压力? cmov 本身在 32 位模式下并不慢。 (agner.org/optimize)。在 Aki 的 SBB/AND 答案中查看我的 cmets:如果您可以节省一个寄存器,则在标志设置之前对寄存器进行异或归零比 SBB/AND 便宜。
  • 昂贵,因为 GCC 说“asm 操作数有不可能的约束”,因为缺少一次性寄存器。
  • @PeterCordes 你的 cmets 还在为 Sandy Bridge 拱门有效吗?
  • 等等什么?您正在使用 inline asm?这是一个巨大的 inline-asm 块的一部分吗?也许编写一个完整的函数,以便您可以根据需要溢出/重新加载。或者如果这只是一个小sn-p,那么gcc.gnu.org/wiki/DontUseInlineAsm: 使用C ? : 三元来鼓励gcc 去无分支。是的,xor-zeroing is fantastically cheap on Sandybridge,与 NOP 一样高效:前端(融合域)只需 1 uop,未融合域中不需要执行单元。
  • 顺便说一句,您可以在全局范围内的 GNU C“基本”asm 语句中编写整个函数。例如在任何函数之外:asm(".globl func\n\t" "func:\n\t" ... ); 然后您可以完全控制寄存器分配,并且可以使用堆栈(这在函数内部的 x86-64 内联 asm 中是不安全的:stackoverflow.com/questions/34520013/…

标签: assembly x86 flags branch-prediction status-register


【解决方案1】:

在大多数 ISA 中基本上有一种通用方法提供分支设置或清除寄存器:从进位标志生成全零或全一掩码:sbb reg,reg 在进位为零时清除掩码,并在设置进位时设置掩码.后跟and dst, reg 将清除目标寄存器,或保持不变。

可以通过切换掩码或反转进位标志来反转条件。零测试可以通过从被测寄存器中减一或从零中减去被测寄存器来实现。第一组进位当且仅当寄存器为零;第二种形式设置进位 iff 寄存器非零。

【讨论】:

  • 如果xor-zero / cmov 太贵,sbb / and 也好不到哪里去。 sbb 在 Intel pre-broadwell 上是 2 uops(与 cmov 相同),并且对寄存器的旧值具有错误的依赖关系,但 AMD Bulldozer 系列(和 Ryzen?)除外,其中 sbb same,same 被认为仅依赖于CF。此外,sbb/and 从标志到结果有 3 个周期的总延迟,因为 AND 位于关键路径上。唯一的优点是在设置 CF 的指令之后 之前不需要额外的寄存器。 xor/cmov 需要在标志设置指令之前进行异或归零。
  • 如果你想反转掩码,可以使用xor eax,eax / set flags / setc al / dec eax。尽管额外的异或归零也很糟糕。取决于您所针对的 uarch,但如果您想反转条件,则更倾向于使用cmov,而不是创建掩码并使用and。 (使用 BMI2,您可以使用 andn 而不是 and 来反转 + 并且一步完成,所以这在 Ryzen / Excavator 上非常好,在 Broadwell / Skylake 上可能还不错)
【解决方案2】:

这可能会让你失望,但CMOVcc 在这方面做得很好。将其与值为 0 的变量 ddZERO 一起使用并没有那么糟糕,尤其是在循环中。

CMOVcc rTarget, ddZERO

如果满足cc 条件,则将rTarget 寄存器重置为零。
否则(有其他情况)您可以在 NOT MATCHING 条件下反转场景和CMOVcc。哪种选择更好取决于发生的频率。

如果你有一个值为0 的寄存器,你应该使用它。但是,如果您不能使用(缓存的)内存位置来节省寄存器,那也不是那么糟糕。此估计基于经验,并且 IIRC 在 L1 缓存内存位置中使用常量在循环中的延迟实际上可以忽略不计。

【讨论】:

  • 是的,内存中0 的微融合负载应该没问题,除非您的代码在负载 uops / L1D 吞吐量方面遇到瓶颈。无条件读取cmov 的内存操作数,并在地址准备好后立即开始加载,在标志和目标寄存器准备好之前,因此通常在其他操作数准备好之前,内存源输入已经准备好。
猜你喜欢
  • 2013-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多