如何有效地计算128位整数(uint128_t
)中前导零的数量?
我知道GCC的内置功能:
__builtin_clz
,__builtin_clzl
,__builtin_clzll
__builtin_ffs
,__builtin_ffsl
,__builtin_ffsll
但是,这些函数仅适用于32位和64位整数。
我还发现了一些SSE指令:
__lzcnt16
,__lzcnt
,__lzcnt64
正如您可能猜到的,这些仅适用于16位,32位和64位整数。
对于128位整数,是否有类似的,高效的内置功能?
答案 0 :(得分:5)
inline int clz_u128 (uint128_t u) {
uint64_t hi = u>>64;
uint64_t lo = u;
int retval[3]={
__builtin_clzll(hi),
__builtin_clzll(lo)+64,
128
};
int idx = !hi + ((!lo)&(!hi));
return retval[idx];
}
这是一个免费分支变体。请注意,与分支解决方案相比,已完成更多工作,实际上分支可能是可预测的。
它还依赖于__builtin_clzll
在输入0时没有崩溃:文档说结果是未定义的,但是它是未指定的还是未定义的?
答案 1 :(得分:4)
假设一个'随机'分布,第一个非零位将处于高64位,具有压倒性的概率,因此首先测试这一半是有意义的。
查看为:
生成的代码/* inline */ int clz_u128 (uint128_t u)
{
unsigned long long hi, lo; /* (or uint64_t) */
int b = 128;
if ((hi = u >> 64) != 0) {
b = __builtin_clzll(hi);
}
else if ((lo = u & ~0ULL) != 0) {
b = __builtin_clzll(lo) + 64;
}
return b;
}
我希望gcc使用__builtin_clzll
指令实现每个bsrq
- 位扫描反转,即最重要的位位置 - 与xor
,{{1}结合使用},或(msb ^ 63)
,sub
,将其变为前导零计数。 gcc可能会使用正确的(63 - msb)
(架构)选项生成lzcnt
条指令。
编辑:其他人已经指出'分布'在这种情况下不相关,因为HI uint64_t需要进行测试。
答案 2 :(得分:3)
只要gcc支持,Yakk的答案适用于各种目标 目标的128位整数。但请注意,在x86-64平台上, 使用英特尔Haswell处理器或更新版本,有一个更有效的解决方案:
#include <immintrin.h>
#include <stdint.h>
// tested with compiler options: gcc -O3 -Wall -m64 -mlzcnt
inline int lzcnt_u128 (unsigned __int128 u) {
uint64_t hi = u>>64;
uint64_t lo = u;
lo = (hi == 0) ? lo : -1ULL;
return _lzcnt_u64(hi) + _lzcnt_u64(lo);
}
_lzcnt_u64内部编译(gcc 5.4)到lzcnt指令,这很好 为零输入定义(它返回64),与gcc的__builtin_clzll()相反。 三元运算符编译为cmove指令。