【问题标题】:Unset the most significant bit in a word (int32) [C]取消设置字中的最高有效位 (int32) [C]
【发布时间】:2011-08-26 01:15:49
【问题描述】:

如何取消设置字的最高有效位(例如 0x00556844 -> 0x00156844)? gcc 中有一个__builtin_clz,但它只计算零,这对我来说是不需要的。另外,我应该如何将 __builtin_clz 替换为 msvc 或 intel c 编译器?

当前我的代码是

 int msb = 1<< ((sizeof(int)*8)-__builtin_clz(input)-1);
 int result = input & ~msb;

更新:好的,如果你说这段代码相当快,我会问你,我应该如何为这段代码添加可移植性?此版本适用于 GCC,但适用于 MSVC 和 ICC?

【问题讨论】:

  • “一个单词的最高有效位”,是第 22 位吗?那就是我在你的例子中看到的
  • 不,它是给定 int 中设置的最高有效位。对于 0x12345678,结果将为 0x02345678;对于 0x00000123 -> 0x00000023
  • 您的实现非常高效,实际上它比我的答案要好,因为编译器会优化减法
  • 为了可移植性,您还应该使用(sizeof(int)*CHAR_BIT)CHAR_BITlimits.h)而不是(sizeof(int)*8)
  • David X,谢谢,但不需要如此广泛的可移植性。对可移植性感兴趣的只是最常见的 x86 和 x86_64 编译器。此代码将由少数用户在桌面和小型集群上使用。

标签: c micro-optimization bitarray


【解决方案1】:

如果您真的关心性能,清除 msb 的最佳方法最近已针对 x86 进行了更改,并添加了 BMI 指令。

在 x86 汇编中:

clear_msb:
    bsrq    %rdi, %rax
    bzhiq   %rax, %rdi, %rax
    retq

现在用 C 重写并让编译器发出这些指令,同时为非 x86 架构或不支持 BMI 指令的旧 x86 处理器优雅地降级。

与汇编代码相比,C 版本实在是丑陋和冗长。但至少它满足了便携性的目标。如果你有必要的硬件和编译器指令(-mbmi、-mbmi2)来匹配,编译后你又回到了漂亮的汇编代码。

正如所写,bsr() 依赖于 GCC/Clang 内置函数。如果针对其他编译器,您可以替换为等效的可移植 C 代码和/或不同的编译器特定的内置函数。

#include <inttypes.h>
#include <stdio.h>

uint64_t bsr(const uint64_t n)
{
        return 63 - (uint64_t)__builtin_clzll(n);
}

uint64_t bzhi(const uint64_t n,
              const uint64_t index)
{
        const uint64_t leading = (uint64_t)1 << index;
        const uint64_t keep_bits = leading - 1;
        return n & keep_bits;
}

uint64_t clear_msb(const uint64_t n)
{
        return bzhi(n, bsr(n));
}

int main(void)
{
        uint64_t i;
        for (i = 0; i < (uint64_t)1 << 16; ++i) {
                printf("%" PRIu64 "\n", clear_msb(i));
        }
        return 0;
}

正如最初提出的问题一样,汇编和 C 版本都可以自然地被 32 位指令替换。

【讨论】:

  • Wiki 说 BMI 扩展可从 Haswell en.wikipedia.org/wiki/Bit_Manipulation_Instruction_Sets 获得。我可以在 32 位模式 x86 和 64 位 x86_64 模式下使用它吗? bsrq/bzhiq 有内置函数吗? (最近的 Clang/gcc 可移植性还可以)
  • @osgx 对于 32 位指令,您可以将程序集中的“q”后缀替换为“l”。或者在 C 版本中,无论您在哪里看到 uint64_t 都在 uint32_t 中交换。至于带有 bsr/bzhi 的内置函数,在 GCC/Clang 中有 intrinsics 可用(包括“immintrin.h”),它允许您本机使用 BMI 指令,尽管您的代码不会优雅地降级 - 它将在不支持 BMI 的硬件上捕获为非法指令。不要太在意 C 代码的冗长:当您添加 -mbmi -mbmi2 并拥有 Haswell 或更新的 CPU 时,它实际上会编译为仅 bsr/bzhi。
【解决方案2】:

你可以的

unsigned resetLeadingBit(uint32_t x) {
    return x & ~(0x80000000U >> __builtin_clz(x))
}

对于 MSVC,有 _BitScanReverse,即 31-__builtin_clz()。

其实反过来,BSR 是自然的 x86 指令,而 gcc 内部是作为 31-BSR 实现的。

【讨论】:

    【解决方案3】:

    只需向下舍入到最接近的 2 次方,然后将其与原始值进行异或,例如使用来自Hacker's Delightflp2()

    uint32_t flp2(uint32_t x) // round x down to nearest power of 2
    {
        x = x | (x >> 1); 
        x = x | (x >> 2); 
        x = x | (x >> 4); 
        x = x | (x >> 8); 
        x = x | (x >>16); 
        return x - (x >> 1); 
    }
    
    uint32_t clr_msb(uint32_t x) // clear most significant set bit in x
    {
        msb = flp2(x);  // get MS set bit in x
        return x ^ msb; // XOR MS set bit to clear it
    }
    

    【讨论】:

    • @osgx:这取决于您要在哪个 CPU 上运行它 - 并非所有 CPU 都有计数前导零指令或等效指令。当然还有便携性的问题……
    • 我的主 CPU 是 x86 (Core2) 和可能是 x86_64 (Core2),带有 clz 指令。只对最常见的 x86 和 x86_64 编译器感兴趣。
    猜你喜欢
    • 2017-11-18
    • 2015-09-22
    • 1970-01-01
    • 1970-01-01
    • 2020-09-07
    • 1970-01-01
    • 2011-05-05
    • 2013-11-13
    • 1970-01-01
    相关资源
    最近更新 更多