没有一条指令可以做到这一点。 BMI1 blsi dst,src 可以隔离 lowest 设置位,而不是最高位。即x & -x。如果 x86 有 blsi 的位反转版本,我们可以使用它,但它没有。
但你可以做得比你建议的要好得多。全零输入始终是位扫描和移位的特殊情况。否则,我们的输出恰好设置了 1 位。我是1 << bsr(input)。
;; input: x in RDI
;; output: result in RAX
isolate_msb:
xor eax, eax ; tmp = 0
bsr rdi, rdi ; edi = bit index of MSB in input
jz .input_was_zero
bts rax, rdi ; rax |= 1<<edi
.input_was_zero: ; return 0 for input=0
ret
显然,对于 32 位输入,仅使用 32 位寄存器。如果不可能为零,则省略 JZ。使用 BSR 代替 LZCNT 给了我们一个位索引,而不是 31-bitidx,所以我们可以直接使用它。但 LZCNT 在 AMD 上的速度明显更快。
异或归零偏离关键路径,为 BTS 准备输入。 xor-zero + BTS 是在 Intel CPU 上实现1<<n 的最有效方式。在 AMD 上它是 2 uop 和 2c 延迟,所以 mov rax,1 / shl rax,cl 会更好。但在 Intel 上更糟,因为变量计数移位是 3 微秒,除非你使用 BMI2 shlx。
无论如何,这里真正的工作是 BSR + BTS,因此在英特尔 SnB 系列上是 3 个周期 + 1 个周期的延迟。 (https://agner.org/optimize/)
在 C / C++ 中,你可以这样写
unsigned isolate_msb32(unsigned x) {
unsigned bitidx = BSR32(x);
//return 1ULL << bitidx; // if x is definitely non-zero
return x ? 1U << bitidx : x;
}
unsigned isolate_msb64(uint64_t x) {
unsigned bitidx = BSR64(x);
return x ? 1ULL << bitidx : x;
}
BSR32 是根据编译器支持的内在函数定义的。这就是事情变得棘手的地方,尤其是如果您想要 64 位版本。没有单一的可移植内在函数。 GNU C 提供了 count-leading-zeros 内在函数,但 GCC 和 ICC 将 63-__builtin_clzll(x) 优化回只是 BSR。相反,他们否定了两次。 有专门用于 BSR 的内置程序,但这些内置程序甚至比 MSVC 和支持 GNU 扩展 (gcc/clang/ICC) 的编译器更特定于编译器。
#include <stdint.h>
// define BSR32() and BSR64()
#if defined(_MSC_VER) || defined(__INTEL_COMPILER)
#ifdef __INTEL_COMPILER
typedef unsigned int bsr_idx_t;
#else
#include <intrin.h> // MSVC
typedef unsigned long bsr_idx_t;
#endif
static inline
unsigned BSR32(unsigned long x){
bsr_idx_t idx;
_BitScanReverse(&idx, x); // ignore bool retval
return idx;
}
static inline
unsigned BSR64(uint64_t x) {
bsr_idx_t idx;
_BitScanReverse64(&idx, x); // ignore bool retval
return idx;
}
#elif defined(__GNUC__)
#ifdef __clang__
static inline unsigned BSR64(uint64_t x) {
return 63-__builtin_clzll(x);
// gcc/ICC can't optimize this back to just BSR, but clang can and doesn't provide alternate intrinsics
}
#else
#define BSR64 __builtin_ia32_bsrdi
#endif
#include <x86intrin.h>
#define BSR32(x) _bit_scan_reverse(x)
#endif
On the Godbolt compiler explorer、clang 和 ICC 无分支编译,即使他们不知道 x 不为零。
所有 4 个编译器都无法使用 bts 来实现 1<<bit。 :( 在 Intel 上非常便宜。
# clang7.0 -O3 -march=ivybridge (for x86-64 System V)
# with -march=haswell and later it uses lzcnt and has to negate. /sigh.
isolate_msb32(unsigned int):
bsr ecx, edi
mov eax, 1
shl rax, cl
test edi, edi
cmove eax, edi # return 1<<bsr(x) or x (0) if x was zero
ret
GCC 和 MSVC 生成分支代码。例如
# gcc8.2 -O3 -march=haswell
mov eax, edi
test edi, edi
je .L6
bsr eax, edi
mov edi, 1
shlx rax, rdi, rax # BMI2: 1 uop instead of 3 for shl rax,cl
.L6:
ret