5

有人告诉我代码中的分支

int value = //some number;
if(value > some_other_value)
   value *= 23;
else
   value -= 5; 

可以通过按位掩码消除(为了启用代码的 SIMD 优化):

const int Mask = (some_other_value-value)>>31;
value =      ((value * 23)&Mask)|((value-5)&~Mask);

但是,我不明白这是如何工作的(即使我了解这里使用了哪些操作以及结果在二进制中的外观)。此外,这是否普遍适用?如果原始代码改为类似

if(value & 1 == 1)
   value *= 23;
else
   value -= 5;

删除分支的代码是否仍然相同?否则,面具的目的是什么,我应该如何去创造它?这里发生了什么?

4

2 回答 2

4

这有效:

const int Mask = (some_other_value-value)>>31;
value =      ((value * 23)&Mask)|((value-5)&~Mask);

掩码成为some_other_value - value- 的符号位,类似于:

if (value > some_other_value) mask = -1; else mask = 0; 

你可以用你的第二个例子来实现同样的事情,使用:

mask = -(value & 1);

所以,-0 = 0,-1 = 全1。

编辑:我还要记住,如果计算变得太复杂,你不会比分支版本获得任何东西,特别是如果分支是可以合理预测的。

于 2013-01-29T14:07:40.747 回答
0

这在最好的情况下是过早的优化,在最坏的情况下是反优化。

如果代码可以向量化,它无论如何都会使用条件移动,因为 SIMD 不知道其他任何东西。

但即使对于标量代码,现代编译器通常也会生成条件移动,因此没有分支(除非编译器认为评估两个方程的成本足够高,因此分支效率更高)。

条件移动一直是 RISC 处理器(例如 ARM)上的标准功能,甚至在 x86 上也支持了大约 17 年。在现代处理器上,条件移动将花费与正常移动完全相同的周期数,或者最多额外花费 2-3 个周期。
这显然假设条件已足够早地评估(尽管是否不依赖于该值并不重要,因为乱序执行会隐藏它),但对于任何类型的隐秘优化黑客都是如此也申请。您只是不能使用尚不存在的结果。

如果你能帮上忙,请始终编写乍一看可以理解的代码而不是一些混淆的代码

value = (((foo<<31)&bar, ++baz) -= (foo & 7121)) + PHASE_OF_MOON;

那种东西,不仅不会更快而且可能更慢,而且还会让审查你的代码的人感到困惑(包括你自己,从现在开始的 6-10 个月内!),是高度不可移植的,而且很可能还会产生不正确的结果你没有预料到的情况。

于 2013-01-30T13:56:05.623 回答