将 0 映射到任何非零值同时保留其他值的无分支方式?
Branchless way to map 0 to any nonzero value while leaving other values alone?
我正在尝试找到该函数的最快实现方式:
uint16_t forbid_zero(uint16_t x)
{
if(x == 0)
return SOMETHING_NONZERO;
return x;
}
SOMETHING_NONZERO 是什么并不重要,只要它不为零即可。除零以外的任何值都应不加修改地通过。这样做最快的位黑客是什么?我假设有一些不错的无分支方式。
对于上下文,我在我的关键路径中有一个算法,其中零作为输入值将触发无限循环和其他不良行为,我很好奇我是否可以将输入修改为始终为非零而不分支检查 0。将不正确的非零值传递给算法的后果几乎没有那么糟糕;该错误将被其他层已经存在的检查发现,因此将 0 映射到任何其他值就足够了。
一种可能的实现方式是:
uint16_t forbid_zero(uint16_t x)
{
return x | !x;
}
Compile Explorer 显示 x86-64 gcc 8.2 compiles to:
forbid_zero(unsigned short):
xorl %eax, %eax
testw %di, %di
sete %al
orl %edi, %eax
ret
然而,即使您在问题 compiles to branchless code using a conditional move instruction 中使用相同的编译器提供的实现:
forbid_zero(unsigned short):
testw %di, %di
movl , %eax
cmovne %edi, %eax
ret
...当然也不能保证 !x
不会被编译到分支。
我正在尝试找到该函数的最快实现方式:
uint16_t forbid_zero(uint16_t x)
{
if(x == 0)
return SOMETHING_NONZERO;
return x;
}
SOMETHING_NONZERO 是什么并不重要,只要它不为零即可。除零以外的任何值都应不加修改地通过。这样做最快的位黑客是什么?我假设有一些不错的无分支方式。
对于上下文,我在我的关键路径中有一个算法,其中零作为输入值将触发无限循环和其他不良行为,我很好奇我是否可以将输入修改为始终为非零而不分支检查 0。将不正确的非零值传递给算法的后果几乎没有那么糟糕;该错误将被其他层已经存在的检查发现,因此将 0 映射到任何其他值就足够了。
一种可能的实现方式是:
uint16_t forbid_zero(uint16_t x)
{
return x | !x;
}
Compile Explorer 显示 x86-64 gcc 8.2 compiles to:
forbid_zero(unsigned short):
xorl %eax, %eax
testw %di, %di
sete %al
orl %edi, %eax
ret
然而,即使您在问题 compiles to branchless code using a conditional move instruction 中使用相同的编译器提供的实现:
forbid_zero(unsigned short):
testw %di, %di
movl , %eax
cmovne %edi, %eax
ret
...当然也不能保证 !x
不会被编译到分支。