我有一个二进制标志f
,等于零或一。如果等于1,我想转换为0xFF
,否则,转换为0。
目前的解决方案是f*0xFF
,但我更愿意使用比特旋转来实现这一点。
怎么样:
(unsigned char)-f
或者:
0xFF & -f
如果f
已经是char
,那么您只需要-f
。
这种方法之所以有效,是因为-0 == 0
和-1 == 0xFFFFF...
,所以否定直接得到你想要的,如果f
大于char
(你没有说),可能会设置一些额外的高位。
尽管如此,请记住编译器是聪明的。我尝试了以下所有解决方案,所有的解决方案都编译为3条或更少的指令,但没有一条有分支(即使是带条件的解决方案):
有条件的
int remap_cond(int f) {
return f ? 0xFF : 0;
}
编译为:
remap_cond:
test edi, edi
mov eax, 255
cmove eax, edi
ret
因此,在大多数现代x86硬件上,即使是"显而易见"的条件也能很好地工作,只需三条指令,延迟2或3个周期,这取决于cmov
的性能。
乘法
您的原始解决方案:
int remap_mul(int f) {
return f * 0xFF;
}
实际上,它编译成了完全避免乘法的漂亮代码,用移位和减法代替:
remap_mul:
mov eax, edi
sal eax, 8
sub eax, edi
ret
在具有mov消除的机器上,这通常需要两个周期,并且mov
通常会通过内联来删除。
减法
正如corn3lius所指出的,你可以从0x100
和掩码中做一些减法,比如:
int remap_shift_sub(int f) {
return 0xFF & (0x100 - f);
}
这编译为1:
remap_shift_sub:
neg edi
movzx eax, dil
ret
因此,我认为这是迄今为止最好的——大多数主机的延迟为2个周期,movzx
通常可以通过内联2来消除——例如,因为它可以在随后的消费指令中使用8位寄存器。
请注意,编译器巧妙地消除了屏蔽操作(你可能会认为movzx
说明了这一点)和0x100
常数的使用,因为它知道简单的否定在这里也起到了同样的作用(特别是,-f
和0x100 - f
之间的所有不同位都被0xFF & ...
操作屏蔽掉了)。
这直接导致以下C代码:
int remap_neg_mask(int f) {
return -f;
}
它汇集了完全相同的东西。
你可以在godbolt上玩所有这些。
1除了在clang
上,它插入一个额外的mov
以在eax
中获得结果,而不是首先在那里生成结果。
2注意,我所说的"内联"指的是,如果你真的把它写成一个函数,编译器会进行真正的内联,但如果你只是直接在需要的地方进行重映射操作,而没有函数,会发生什么。
value = 0xFF & ((1 << 16) - f )
如果f
是1,则从0x100
中减去它,得到0xFF
;否则用CCD_ 26减去0和位掩码得到CCD_。
太明显了
value = ( f == 1 ) ? 0xFF : 0;