因此对于以下序列: 0001000111000
所需的结果将是: 0001000000000
我完全意识到,这可以通过以下方式实现:使用程序集BSRL(或类似的比特混乱hack)找到MSB的索引,然后>>将数字移位(index-1),然后<<移位(index- 1), 但是我想知道,具体来说,是否存在汇编指令或具有更好性能的指令序列,而不是可以做到这一点的琐事。
答案 0 :(得分:5)
没有单个指令可以执行此操作。 BMI1 blsi dst,src
可以隔离最低设置位,而不是最高位。即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微秒的延迟为2c,因此mov rax,1
/ shl rax,cl
会更好。但是在Intel上更糟,因为除非使用BMI2 shlx
,否则可变计数移位为3 oups。
无论如何,这里的实际工作是BSR + BTS,因此Intel SnB系列产品的延迟为3个周期+ 1个周期。 (https://agner.org/optimize/)
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提供了计数前导零的内在函数,但是GCC和ICC擅长将63-__builtin_clzll(x)
优化回BSR。相反,他们两次否定。有专门用于BSR的 内置程序,但它们不仅是MSVC还是支持GNU扩展(gcc / clang / ICC)的编译器,而不仅仅是MSVC。
#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
答案 1 :(得分:-1)
关于您要问的问题,没有单一的说明。
但是,如果要避免使变量的位发生混乱,则有另一种方法:
声明与原始变量相同类型的第二个变量,并将第二个变量设置为0。然后从最高位到最低位循环遍历原始变量的位,并用&
测试每一位操作员。如果找到设置为1的位,则在第二个变量中设置相应的位,然后退出循环。如果需要,将第二个变量分配给原始变量。