【问题标题】:How to use MSVC intrinsics to get the equivalent of this GCC code?如何使用 MSVC 内在函数来获得此 GCC 代码的等效项?
【发布时间】:2010-09-26 05:44:00
【问题描述】:

以下代码在 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?我已经看过this page,但我不完全确定#pragma 的用途(它是必需的吗?)以及它对编译的MSVC 版本要求有什么限制。作为一个没有真正使用 MSVC 的人,我也不知道这些内在函数在其他架构上是否具有 C 等价物,或者在#defining 它们时是否也必须 #ifdef x86/x86_64。

【问题讨论】:

  • 您上面提到的页面指的是一个属于 .NET 运行时的函数,您是要为 .NET 构建程序还是作为本机 Windows 可执行文件?
  • 这是一个原生的 Windows 可执行文件——我问的部分原因是我发现现在很难找到真正谈论 C 的 Microsoft 文档页面。

标签: c visual-c++ intrinsics


【解决方案1】:

从 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),但这是一个选择

【讨论】:

  • MSVC 中 __builtin_clz() 的几乎一对一替换是 __lzcnt()。不过硬件必须支持 SSE4。 More info.
  • 我的硬件支持 SSE4,但不支持 BMI1,所以 __lzcnt() 可以编译,但没有达到我的预期,而是作为 BSR 工作。
  • 31 ^__builtin_clz 等于 _BitScanReverse
  • 请注意,当输入值为0 (gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) 时,GNU C __builtin_ctz 和 clz 也有未定义的行为;这允许它们作为单个bsf 指令内联(或31 ^ bsr,适用于定义的输出范围)。如果您需要处理可能为零的输入,那么您需要类似的 GNU C 内置包装器,因此适当的做法是在 BSF / 31^BSR 周围有一个可移植层,然后在此之上进行零处理。 .. 并使用 lzcnt #ifdef __BMI1__.
  • 相关:VS: unexpected optimization behavior with _BitScanReverse64 intrinsic - MSVC 不会公开 asm 指令的目标未修改行为,即使它有一个 可以 这样做的 API。 (所以你不需要初始化index 输出参数;不过它没有什么坏处,编译器知道它是内在函数的仅输出操作数。)
【解决方案2】:

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

Compiler Intrinsics on MSDN

否则,您将不得不使用 __asm 编写它

【讨论】:

    【解决方案3】:

    我在一个韩国网站上找到它https://torbjorn.tistory.com/317 在msvc编译器中,可以使用__lzcnt(unsigned int)替换gcc编译器中的__builtin_clz(unsigned int)

    【讨论】:

    • 请注意,lzcnt 指令需要 BMI1。在较旧的 CPU 上,它以bsr 执行,给出31-lzcnt(并且对于输入=0 保持目标寄存器不变)。如果您使用-march=haswell 或类似选项进行编译,GCC 只会将__builtin_clz 扩展为lznct
    【解决方案4】:
    1. MSVC 中 int __builtin_ctz (unsigned int x) 的等效函数是 unsigned int _tzcnt_u32 (unsigned int a) 表示 32 位 整数并返回尾随零的计数。对于 64 位,请使用 unsigned __int64 _tzcnt_u64 (unsigned __int64 a) 1

    2. MSVC 中 int __builtin_clz (unsigned int x) 的等效函数是 unsigned int _lzcnt_u32 (unsigned int a) 表示 32 位 整数并返回前导零的计数。对于 64 位 使用 unsigned __int64 _lzcnt_u64 (unsigned __int64 a) 2

    C++ 头文件:immintrin.h

    【讨论】:

    • 并非所有计算机都有 BMI1,因此 lzcnt 可能会解码为 bsr 并给出 31-clz 而不是您期望的 clzbsr 有一个 MSVC 内在函数,特别是 _BitScanReverse。只有当您使用 gcc -mbmi(或者当然是 gcc -march=haswell 或包含 BMI1 的东西)编译时,它们才是等效的。请参阅Does x64 support imply BMI1 support? 了解这些内在函数的旧硬件兼容性问题(以及当前的 Pentium / Celeron 低端 CPU,感谢 Intel)。这是一个有用的答案,但只有edit 才能提及。
    • 所有处理器可能不支持 BMI1 指令集。但是,基于对 BMI1 指令可用性的检测,可以使用 lzcnt。否则可以使用 bsr。
    • 是的,完全正确。您必须检查您的 CPU 并手动使用 clz = 31-bsr(x); 在没有 BMI1 的 CPU 上精确模拟 __builtin_clz。但你的回答并没有这么说。它错误地暗示 _lzcnt_u32 通常会给您与 __builtin_clz 相同的结果。但与 GCC 不同的是,MSVC 允许您使用内在函数,而无需使用任何 -march=haswell 进行编译,相当于“承诺”您只会在支持某些指令集扩展的 CPU 上运行二进制文件。
    • (顺便说一句,要在没有 BMI1 的情况下精确模拟 _lzcnt_u32,您需要 lzcnt = x==0 ? 32 : 31-bsr(x);__builtin_clz 如果使用输入 0 运行,则具有未定义的行为,允许它编译为bsr(x) ^ 31,但 lzcnt 对输入 == 0 具有明确定义的行为。)
    • 我不明白什么是“手动使用 clz = 31-bsr(x)”以及为什么要为输入 == 0 获得前导/尾随零计数?跨度>
    【解决方案5】:

    在 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;
    }
    

    【讨论】:

    • 你测试过你的clz64的性能吗?我不会对所有这些分支使它比 OP 的实现更慢感到惊讶。
    • 如果您想在 GNU C 上支持 64 位整数,请像普通人一样使用 __builtin_clzll。这样编写它可能会阻止 GCC 使用单个 64 位 bsrlzcnt在 64 位版本中。 (但你也可以使用 64 位 MSVC 内在函数。)
    【解决方案6】:

    有两个内在函数“_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 Intrinsics on MSDN

    【讨论】:

    • ctz 和 clz 调用了错误的函数(它们应该分别使用 _BitScanForward 和 BitScanReverse,而不是 BitScanReverse/BitScanForward)& clz 是错误的,因为它返回的是位集的偏移量而不是前导零的数量.
    猜你喜欢
    • 2019-02-01
    • 2014-11-14
    • 1970-01-01
    • 2021-11-27
    • 2012-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-04
    相关资源
    最近更新 更多