问题 如何使用MSVC内在函数来获得相当于这个GCC代码?


以下代码在GCC中调用clz / ctz的内置函数,在其他系统上调用C版本。显然,如果系统有内置的clz / ctz指令,如x86和ARM,则C版本有点不理想。

#ifdef __GNUC__
#define clz(x) __builtin_clz(x)
#define ctz(x) __builtin_ctz(x)
#else
static uint32_t ALWAYS_INLINE popcnt( uint32_t x )
{
    x -= ((x >> 1) & 0x55555555);
    x = (((x >> 2) & 0x33333333) + (x & 0x33333333));
    x = (((x >> 4) + x) & 0x0f0f0f0f);
    x += (x >> 8);
    x += (x >> 16);
    return x & 0x0000003f;
}
static uint32_t ALWAYS_INLINE clz( uint32_t x )
{
    x |= (x >> 1);
    x |= (x >> 2);
    x |= (x >> 4);
    x |= (x >> 8);
    x |= (x >> 16);
    return 32 - popcnt(x);
}
static uint32_t ALWAYS_INLINE ctz( uint32_t x )
{
    return popcnt((x & -x) - 1);
}

#endif

我需要调用哪些函数,我需要包含哪些标题等,以便在此处为MSVC添加正确的ifdef?我已经看过了 这一页,但我不完全确定#pragma是什么(它是否需要?)以及它对编译的MSVC版本要求有什么限制。作为一个并不真正使用MSVC的人,我也不知道这些内在函数是否在其他体系结构上具有C等价物,或者在#defining它们时是否还需要#ifdef x86 / x86_64。


12272
2017-12-10 13:00


起源

您在上面引用的页面是指作为.NET运行时的一部分的函数,您是尝试为.NET构建程序还是作为本机Windows可执行文件? - Timo Geusch
这是一个原生的Windows可执行文件 - 我要问的部分原因是我发现现在很难找到真正谈论C的Microsoft文档页面。 - Dark Shikari
Libcxx实现 github.com/llvm-mirror/libcxx/blob/... - KindDragon


答案:


从sh0dan代码反弹,实现应该像这样纠正:

#ifdef _MSC_VER
#include <intrin.h>

uint32_t __inline ctz( uint32_t value )
{
    DWORD trailing_zero = 0;

    if ( _BitScanForward( &trailing_zero, value ) )
    {
        return trailing_zero;
    }
    else
    {
        // This is undefined, I better choose 32 than 0
        return 32;
    }
}

uint32_t __inline clz( uint32_t value )
{
    DWORD leading_zero = 0;

    if ( _BitScanReverse( &leading_zero, value ) )
    {
       return 31 - leading_zero;
    }
    else
    {
         // Same remarks as above
         return 32;
    }
}
#endif

正如在代码中所评论的那样,如果值为0,则ctz和clz都是未定义的。在我们的抽象中,我们修复了 __builtin_clz(value) 如 (value?__builtin_clz(value):32) 但这是一个选择


18
2017-12-09 10:23



几乎是1比1的替代品 __builtin_clz() 在MSVC是 __lzcnt()。硬件必须支持SSE4。 更多信息。 - rustyx
我的硬件支持SSE4,但不支持BMI1,所以__lzcnt()编译但不会做我期望的事情,而是作为BSR工作。 - GregC
31 ^__builtin_clz 等于 _BitScanReverse - KindDragon


如果MSVC有一个内在的编译器,它将在这里:

MSDN上的编译器内在函数

否则,你必须使用__asm编写它


1
2017-12-10 17:33





在linux和windows(x86)上测试:

#ifdef WIN32
    #include <intrin.h>
    static uint32_t __inline __builtin_clz(uint32_t x) {
        unsigned long r = 0;
        _BitScanReverse(&r, x);
        return (31-r);
    }
#endif

uint32_t clz64(const uint64_t x)
{
    uint32_t u32 = (x >> 32);
    uint32_t result = u32 ? __builtin_clz(u32) : 32;
    if (result == 32) {
        u32 = x & 0xFFFFFFFFUL;
        result += (u32 ? __builtin_clz(u32) : 32);
    }
    return result;
}

-3
2017-09-13 12:17



你测试过clz64的性能吗?所有这些分支使得它比OP的实现慢,我不会感到惊讶。 - plamenko


有两个内在函数“_BitScanForward”和“_BitScanReverse”,它们适用于MSVC。包括。功能是:

#ifdef _MSC_VER
#include <intrin.h>

static uint32_t __inline ctz( uint32_t x )
{
   int r = 0;
   _BitScanReverse(&r, x);
   return r;
}

static uint32_t __inline clz( uint32_t x )
{
   int r = 0;
   _BitScanForward(&r, x);
   return r;
}
#endif

有等效的64位版本“_BitScanForward64”和“_BitScanReverse64”。

在这里阅读更多:

x86 MSDN上的内在函数


-3
2018-03-29 06:44



ctz&clz调用错误的函数(它们应该分别使用_BitScanForward和BitScanReverse,而不是BitScanReverse / BitScanForward)&clz是错误的,因为它返回位集的偏移量而不是前导零的数量。 - Vitali