如何制作无网段代码?
与此答案相关: https : //stackoverflow.com/a/11227902/4714970
在上面的答案中,提到了如何通过避免分支来避免分支预测失败。
用户通过替换以下内容来演示:
if (data[c] >= 128) { sum += data[c]; }
附:
int t = (data[c] - 128) >> 31; sum += ~t & data[c];
这两个是等价的(对于特定的数据集,不是严格等同的)?
在类似的情况下,我可以采取哪些一般方法来做类似的事情? 它总是通过使用>>
和~
?
int t = (data[c] - 128) >> 31;
这里的技巧是,如果data[c] >= 128
,那么data[c] - 128
是非负的,否则它是负的。 当且仅当该数字为负时, int
的最高位(符号位)为1。 >>
是一个扩展符号位的移位,因此右移31会使整个结果为0,如果它曾经是非负的,并且所有1位(代表-1)如果以前是负的。 因此,如果data[c] >= 128
则t
为0
,否则为-1
。 ~t
切换这些可能性,因此如果data[c] >= 128
则~t
为-1
,否则为0
。
x & (-1)
总是等于x
, x & 0
总是等于0
。 因此,如果data[c] < 128
,则sum += ~t & data[c]
将sum
加0
,否则加上data[c]
。
其中许多技巧可以应用于其他地方。 当且仅当一个值大于或等于另一个值时,这个技巧当然可以应用于数字为0
,否则为-1
,你可以更多地使用它来获得<=
, <
,等等上。 像这样的小辫子是使数学运算无分支的常用方法,尽管它肯定不会总是用相同的操作构建; ^
(xor)和|
(或)有时也会发挥作用。
虽然Louis Wasserman的回答是正确的,但我想向您展示一种更通用(更清晰)的方法来编写无分支代码。 你可以用? :
? :
运营商:
int t = data[c]; sum += (t >= 128 ? t : 0);
JIT编译器从执行配置文件中看到这里的条件预测不佳。 在这种情况下,编译器足够聪明,可以使用条件移动指令替换条件分支:
mov 0x10(%r14,%rbp,4),%r9d ; load R9d from array cmp $0x80,%r9d ; compare with 128 cmovl %r8d,%r9d ; if less, move R8d (which is 0) to R9d
您可以validation此版本对已排序和未排序的arrays同样快速地工作。
无分支代码通常意味着使用集合[0,1]中的权重来评估条件语句的所有可能结果,以便Sum {weight_i} = 1.大多数计算基本上被丢弃。 一些优化可以由以下事实导致:当对应的权重w_i
(或掩码m_i
)为零时, E_i
不必是正确的。
result = (w_0 * E_0) + (w_1 * E_1) + ... + (w_n * E_n) ;; or result = (m_0 & E_0) | (m_1 & E_1) | ... | (m_n * E_n)
其中m_i代表位掩码。
通过水平折叠并行处理E_i也可以实现速度。
这与if (a) b; else c;
的语义相矛盾if (a) b; else c;
if (a) b; else c;
还是它的三元速记a ? b : c
a ? b : c
,其中仅评估[b,c]中的一个表达式。
因此,三元操作对于无分支代码来说不是神奇的子弹。 一个体面的编译器同样产生无分支代码
t = data[n]; if (t >= 128) sum+=t;
与
movl -4(%rdi,%rdx), %ecx leal (%rax,%rcx), %esi addl $-128, %ecx cmovge %esi, %eax
无分支代码的变化包括通过其他无分支非线性函数(例如ABS)呈现问题(如果存在于目标机器中)。
例如
2 * min(a,b) = a + b - ABS(a - b), 2 * max(a,b) = a + b + ABS(a - b)
甚至:
ABS(x) = sqrt(x*x) ;; caveat -- this is "probably" not efficient
除了<<
和~
,使用bool
和!bool
代替(可能是未定义的)(int >> 31)可能同样有益。 同样,如果条件的计算结果为[0,1],则可以生成带有否定的工作掩码:
-[0, 1] = [0, 0xffffffff] in 2's complement representation