C位旋转使int 0变为1,而任何non-zero变为-1? (理想情况下是 x86 固有的)
C bit twiddling so that int 0 becomes 1, while any non-zero becomes -1? (ideally x86 intrinsic)
我正在寻找一种方法来实现我在标题中所写的内容。
我现在用“如果”来做,我想摆脱分支。
我已经查看了几页,例如 this one,找不到我要找的确切内容。
如果没有位操作,你可以这样做:
int func(int x)
{
return int(uint(x-1) / uint(-1)) * 2 - 1;
}
或者:
int func(int x)
{
return int(uint(~x) / uint(~0)) * 2 - 1;
}
将 x
转换为布尔值不会在当前 x86 处理器上生成任何分支。您可以使用简单的算法来生成结果:
int test_zero(int x) {
return 1 - 2 * !!x;
}
gcc 11.2 生成这个:
test_zero:
cmp edi, 1
sbb eax, eax
and eax, 2
sub eax, 1
ret
clang 13.0.0 生成这个:
test_zero: # @test_zero
xor eax, eax
test edi, edi
sete al
add eax, eax
add eax, -1
ret
正如 dratenik 评论的那样,更简单、更易读的源代码编译成完全相同的无分支可执行代码:
int test_zero2(int x) {
return x ? -1 : 1;
}
您检查 Godbolt's compiler explorer 上的代码生成。
clang/gcc 输出(来自 chqrlie 的回答)可以被截断为
cmp edi, 1
sbb eax, eax
or eax, 1
在 sbb eax, eax
之后 eax == 0
edi != 0
。
但由于 -1 和 1 都设置了 LSB,我们可以这样设置。
唉,即使我们可以为
生成两个指令序列
int test_zero_3(int x) {
return x ? -1 : 0;
}
...
neg edi
sbb eax, eax
我们无法欺骗 clang,但我们可以让 gcc 产生预期的(或接近等效的)序列
int test_zero_4(int x) {
return (test_zero_3(x)) | 1;
}
...
neg edi
sbb eax, eax
or eax, 1
我正在寻找一种方法来实现我在标题中所写的内容。 我现在用“如果”来做,我想摆脱分支。 我已经查看了几页,例如 this one,找不到我要找的确切内容。
如果没有位操作,你可以这样做:
int func(int x)
{
return int(uint(x-1) / uint(-1)) * 2 - 1;
}
或者:
int func(int x)
{
return int(uint(~x) / uint(~0)) * 2 - 1;
}
将 x
转换为布尔值不会在当前 x86 处理器上生成任何分支。您可以使用简单的算法来生成结果:
int test_zero(int x) {
return 1 - 2 * !!x;
}
gcc 11.2 生成这个:
test_zero:
cmp edi, 1
sbb eax, eax
and eax, 2
sub eax, 1
ret
clang 13.0.0 生成这个:
test_zero: # @test_zero
xor eax, eax
test edi, edi
sete al
add eax, eax
add eax, -1
ret
正如 dratenik 评论的那样,更简单、更易读的源代码编译成完全相同的无分支可执行代码:
int test_zero2(int x) {
return x ? -1 : 1;
}
您检查 Godbolt's compiler explorer 上的代码生成。
clang/gcc 输出(来自 chqrlie 的回答)可以被截断为
cmp edi, 1
sbb eax, eax
or eax, 1
在 sbb eax, eax
之后 eax == 0
edi != 0
。
但由于 -1 和 1 都设置了 LSB,我们可以这样设置。
唉,即使我们可以为
生成两个指令序列int test_zero_3(int x) {
return x ? -1 : 0;
}
...
neg edi
sbb eax, eax
我们无法欺骗 clang,但我们可以让 gcc 产生预期的(或接近等效的)序列
int test_zero_4(int x) {
return (test_zero_3(x)) | 1;
}
...
neg edi
sbb eax, eax
or eax, 1