使用按位运算符进行分支消除

sci*_*ete 8 c++ optimization bit-manipulation

我在循环中有一些关键的分支代码,运行大约2 ^ 26次.分支预测不是最优的,因为m是随机的.如何删除分支,可能使用按位运算符?

bool m;
unsigned int a;
const unsigned int k = ...; // k >= 7
if(a == 0)
    a = (m ? (a+1) : (k));
else if(a == k)
    a = (m ?     0 : (a-1));
else
    a = (m ? (a+1) : (a-1));
Run Code Online (Sandbox Code Playgroud)

以下是由gcc -O3以下内容生成的相关程序集:

.cfi_startproc
movl    4(%esp), %edx
movb    8(%esp), %cl
movl    (%edx), %eax
testl   %eax, %eax
jne L15
cmpb    $1, %cl
sbbl    %eax, %eax
andl    $638, %eax
incl    %eax
movl    %eax, (%edx)
ret
L15:
cmpl    $639, %eax
je  L23
testb   %cl, %cl
jne L24
decl    %eax
movl    %eax, (%edx)
ret
L23:
cmpb    $1, %cl
sbbl    %eax, %eax
andl    $638, %eax
movl    %eax, (%edx)
ret
L24:
incl    %eax
movl    %eax, (%edx)
ret
.cfi_endproc
Run Code Online (Sandbox Code Playgroud)

小智 4

无分支无除模可能很有用,但测试表明实际上并非如此。

const unsigned int k = 639;
void f(bool m, unsigned int &a)
{
    a += m * 2 - 1;
    if (a == -1u)
        a = k;
    else if (a == k + 1)
        a = 0;
}
Run Code Online (Sandbox Code Playgroud)

测试用例:

unsigned a = 0;
f(false, a);
assert(a == 639);
f(false, a);
assert(a == 638);
f(true, a);
assert(a == 639);
f(true, a);
assert(a == 0);
f(true, a);
assert(a == 1);
f(false, a);
assert(a == 0);
Run Code Online (Sandbox Code Playgroud)

实际上,使用测试程序对此进行计时:

int main()
{
    for (int i = 0; i != 10000; i++)
    {
        unsigned int a = k / 2;
        while (a != 0) f(rand() & 1, a);
    }
}
Run Code Online (Sandbox Code Playgroud)

(注意:没有srand,因此结果是确定性的。)

我原来的答案:5.3s

问题中的代码:4.8s

查表:4.5s ( static unsigned lookup[2][k+1];)

查表:4.3s ( static unsigned lookup[k+1][2];)

艾里克的答案:4.2秒

本版本:4.0s