根据标志等于1还是0,将标志转换为0xFF或0



我有一个二进制标志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常数的使用,因为它知道简单的否定在这里也起到了同样的作用(特别是,-f0x100 - 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;   

最新更新