取消设置单词中最重要的位(int32)[C]

时间:2011-05-15 20:52:20

标签: c micro-optimization bitarray

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

目前我的代码是

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

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

3 个答案:

答案 0 :(得分:7)

向下舍入到最接近的2的幂,然后将其与原始值进行异或,例如,使用Hacker's Delight中的flp2()

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
}

答案 1 :(得分:6)

如果您真的关心性能,最近清除msb的最佳方法是使用添加BMI指令来更改x86。

在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位指令替换。

答案 2 :(得分:3)

你可以做到

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

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

实际上相反,BSR是自然的x86指令,gcc内部实现为31-BSR。