如何制作无网段代码?

Aeq*_*tas 22 java performance branch-prediction

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

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

用户通过替换以下内容来演示:

if (data[c] >= 128)
{
    sum += data[c];
}
Run Code Online (Sandbox Code Playgroud)

附:

int t = (data[c] - 128) >> 31;
sum += ~t & data[c];
Run Code Online (Sandbox Code Playgroud)

这两个是如何等效的(对于特定的数据集,不是严格等同的)?

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

Lou*_*man 27

int t = (data[c] - 128) >> 31;
Run Code Online (Sandbox Code Playgroud)

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

x & (-1)总是等于x,并且x & 0总是等于0.因此,通过if 和其他方式sum += ~t & data[c]增加.sum0data[c] < 128data[c]

其中许多技巧可以应用于其他地方.当然0并且只有当一个值大于或等于另一个值时,这个技巧当然可以应用于一个数字,-1否则,你可以更多地使用它来获取<=,<等等.这样的比特是一种使数学运算无分支的常用方法,尽管它肯定不会总是用相同的操作构建; ^(xor)和|(或)有时也会发挥作用.


apa*_*gin 16

虽然Louis Wasserman的回答是正确的,但我想向您展示一种更通用(更清晰)的方法来编写无分支代码.你可以使用? :运算符:

    int t = data[c];
    sum += (t >= 128 ? t : 0);
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

您可以验证此版本对已排序和未排序的数组的运行速度同样快.

  • 请注意,当branches*是可预测的时,将控制依赖关系转换为关键路径上的数据依赖关系可能是一件坏事.`cmov`将其输入放入依赖链中.此外,好的编译器通常可以将简单的`if`子句转换为`cmov`. (4认同)
  • `?:`是一个分支,它比`if()`具有优势,它是一个表达而不是一个语句.带有`if()`和两个`return`s的内联函数基本上等同于`?:`.如果它导致无分支机器语言,那是由于优化.有关详细讨论,请参阅@ aki-suihkonen的回答http://stackoverflow.com/a/32113018/260122. (4认同)
  • 这种方法的问题在于您依赖于编译器优化.因此,如果您想确保无分支代码(例如,为了避免加密代码中的时序攻击),使用丑陋的解决方法仍然是一个好主意(即使它不是100%保证). (2认同)
  • @clacke我并不是说它不是一个分支。我也说不是。它是一个运算符,一种语言结构。它可能会也可能不会编译为分支。在Java字节码中存在一个分支(只是因为除了分支之外没有其他条件字节码)。但我想说的是,JIT 编译器可以识别这个特定的字节码模式并将其转换为条件移动指令。 (2认同)

Aki*_*nen 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)
Run Code Online (Sandbox Code Playgroud)

其中m_i代表位掩码.

通过水平折叠并行处理E_i也可以实现速度.

这与语义if (a) b; else c;或它的三元速记相矛盾a ? b : c,其中只评估了[b,c]中的一个表达式.

因此,三元运算对于无分支代码来说不是神奇的子弹.一个体面的编译器同样产生无分支代码

t = data[n];
if (t >= 128) sum+=t;
Run Code Online (Sandbox Code Playgroud)

    movl    -4(%rdi,%rdx), %ecx
    leal    (%rax,%rcx), %esi
    addl    $-128, %ecx
    cmovge  %esi, %eax
Run Code Online (Sandbox Code Playgroud)

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

例如

 2 * min(a,b) = a + b - ABS(a - b),
 2 * max(a,b) = a + b + ABS(a - b)
Run Code Online (Sandbox Code Playgroud)

甚至:

 ABS(x) = sqrt(x*x)      ;; caveat -- this is "probably" not efficient
Run Code Online (Sandbox Code Playgroud)

除了<<和之外~,使用bool!bool代替(可能是未定义的)(int >> 31)也同样有益.同样,如果条件的计算结果为[0,1],则可以生成带有否定的工作掩码:

-[0, 1] = [0, 0xffffffff]  in 2's complement representation
Run Code Online (Sandbox Code Playgroud)