将所有位低于最重要的设置位零的最有效方法是什么?

What is the most efficient way to zero all bits below the most significant set bit?

本文关键字:有效 方法 是什么 置位 最重要的      更新时间:2023-10-16

因此,对于以下序列:0001000111000

所需的结果将是:0001000000000

我完全意识到,可以通过汇编bsrl(或类似的咬合hack)找到MSB的索引,然后>>>>>>将数字移动(index -1),然后<<<通过(索引-1)转移但是我想知道,特别是有一个汇编指令还是一系列具有更好性能的说明,而不是可以做到这一点的有点扭曲。

没有一个指令可以做到这一点。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上的速度明显更快。

XOR-Zeroing不在临界路径上,以准备BTS的输入。XOR-Zero BTS是在Intel CPU上实现1<<n的最有效方法。在AMD上是2C延迟的2个UOP,因此mov rax,1/shl rax,cl在那里更好。但是在英特尔上更糟,因为可变计数偏移为3个UOPS,除非您使用BMI2 shlx

无论如何,这里的真正工作是BSR BTS,因此这是Intel 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提供了计数领域的内在,但是GCC和ICC吸引了将63-__builtin_clzll(x)优化回到BSR中。相反,他们否定了两次。的BSR内置,但是这些是编译器特定的,而不是支持GNU扩展的MSVC与编译器(GCC/Clang/ICC)。Godbolt编译器探险器上,即使他们不知道x不是零。

所有4个编译器都无法使用bts实现1<<bit。:(在英特尔上很便宜。

# 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

您的要求没有单个指令,否。

但是,如果您想避免扭动变量的位,则有一种替代方法:

声明与原始变量相同类型的第二个变量,然后将第二个变量设置为0。然后将原始变量的位循环从最高位到最低位,通过&操作员测试每个位。如果发现一些位设置为1,请在第二个变量中设置相应位,然后退出循环。如果需要,将第二个变量分配给原始变量。