如何制作无网段代码?

时间:2015-08-19 23:10:01

标签: java performance branch-prediction

与此答案相关:https://stackoverflow.com/a/11227902/4714970

在上面的回答中,提到了如何通过避免分支来避免分支预测失败。

用户通过替换:

来演示这一点
if (data[c] >= 128)
{
    sum += data[c];
}

使用:

int t = (data[c] - 128) >> 31;
sum += ~t & data[c];

这两个等价物(对于特定数据集,并非严格等同)?

在类似的情况下,我可以采取哪些一般方法来做类似的事情?是否始终使用>>~

3 个答案:

答案 0 :(得分:27)

int t = (data[c] - 128) >> 31;

这里的诀窍是,如果data[c] >= 128,则data[c] - 128为非负,否则为负。当且仅当该数字为负时,符号位int中的最高位为1。 >>是一个扩展符号位的移位,因此右移31会使整个结果为0(如果它曾经是非负的),并且所有1位(表示-1)如果它曾经是消极的。因此,如果t 0data[c] >= 128,则-1~t~t会切换这些可能性,因此如果-1 data[c] >= 1280,则x & (-1)x

x & 0始终等于0sum += ~t & data[c]始终等于sum。因此,如果0 data[c] < 128 data[c]增加0-1增加<=

其中许多技巧可以应用于其他地方。当且仅当一个值大于或等于另一个值时,这个技巧当然可以应用于<,否则为^,你可以把它弄得更糟|git checkout -b all-files git add --force . # --force ignores .gitignore git commit -m "adding all files" git push origin all-files ,依此类推。这样的比特是一种使数学运算无分支的常用方法,尽管它肯定不会总是用相同的操作构建; git checkout all-files git add --force . # only if you have updated ignored files git commit -m "updating .gitignored files" # only if you have updated ignored files git rebase master git push origin all-files --force (xor)和.gitignore(或)有时会发挥作用。

答案 1 :(得分:15)

虽然Louis Wasserman的回答是正确的,但我想向您展示一种更通用(更清晰)的方法来编写无分支代码。您可以使用 int t = data[c]; sum += (t >= 128 ? t : 0); 运算符:

    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

JIT编译器从执行配置文件中看到此处的情况预测不佳。在这种情况下,编译器足够聪明,可以用条件移动指令替换条件分支:

private void treeView_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
{
    if (e.Button == MouseButtons.Right)
    {
        this.treeView.SelectedNode = e.Node;

您可以验证此版本对已排序和未排序的数组的工作速度同样快。

答案 2 :(得分:9)

无分支代码通常意味着使用集合[0,1]中的权重评估条件语句的所有可能结果,以便Sum {weight_i} = 1.大多数计算基本上都是丢弃。一些优化可能是因为E_i在相应的权重w_i(或掩码m_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;或它的三元速记a ? b : c的语义相矛盾,其中只评估了[b,c]中的一个表达式。

因此,三元操作对于无分支代码来说并不是一个神奇的子弹。一个不错的编译器为

生成无分支代码
t = data[n];
if (t >= 128) sum+=t;

VS

    movl    -4(%rdi,%rdx), %ecx
    leal    (%rax,%rcx), %esi
    addl    $-128, %ecx
    cmovge  %esi, %eax

无分支代码的变化包括通过其他无分支非线性函数(如ABS)呈现问题(如果存在于目标机器中)。

e.g。

 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&gt;&gt; 31)同样有益。同样,如果条件的计算结果为[0,1],则可以生成带否定的工作掩码:

-[0, 1] = [0, 0xffffffff]  in 2's complement representation