计算128位整数中前导零的数量

时间:2015-02-10 03:09:31

标签: c++ gcc bit-manipulation sse

如何有效地计算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位整数,是否有类似的,高效的内置功能?

3 个答案:

答案 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指令。