如何为MSVC Visual Studio指定目标CPU/体系结构Haswell



我有一个程序,它大量使用内部命令_BitScanForward/_BitScanForward64(也称为计数尾随零、TZCNT、CTZ)。我希望不要使用内部指令,而是使用相应的CPU指令(在Haswell及更高版本上可用)。

当使用gcc或clang(其中内部函数称为__builtin_ctz)时,我可以通过指定-march=haswell-mbmi2作为编译器标志来实现这一点。

BitScanForward的文档仅指定内在在所有体系结构上可用";x86、ARM、x64、ARM64";或";x64、ARM64";,但我不只是想让它可用,我还想确保它被编译为使用CPU指令,而不是内部函数。我也检查了/Oi,但这也不能解释。

我也在网上搜索过,但奇怪的是,我的问题几乎没有匹配项,大多数只是解释如何使用内部词,例如这个问题和这个问题。

我是不是想得太多了,如果CPU支持,MSVC会创建神奇地使用CPU指令的代码?是否需要任何标志?如何确保CPU指令在可用时得到使用?

更新

以下是Godbolt的外观。请注意,我的汇编阅读能力很基本。

GCC使用带有haswell/bmi2的tzcnt,否则使用rep bsf。MSVC使用bsf而不使用rep

我还发现了这个有用的答案,它指出:

  • "为bsr使用冗余rep前缀通常被定义为被忽略[…]";。我想知道bsf是否也是如此
  • 它解释了(正如我所知)bsftzcnt不同,但是MSVC似乎没有检查输入==0

这增加了问题:为什么bsf适用于MSVC?

更新

好吧,这很简单,我实际上为MSVC打了_BitScanForward。Doh!

更新

所以我在这里添加了一些不必要的混乱。理想情况下,我想使用一个内在的__tzcnt,但它在MSVC中不存在,所以我使用_BitScanForward加上一个额外的检查来解释0输入。

然而,MSVC支持LZCNT,我也有类似的问题(但在我的代码中使用较少)。

稍微更新一下的问题是:MSVC如何处理LZCNT(而不是TZCNT)?

答案:请参阅此处。具体地说:";在不支持lzcnt指令的英特尔处理器上,指令字节编码执行为bsr(反向位扫描)。如果代码的可移植性是一个问题,那么考虑使用_BitScanReverse内在">

这篇文章建议,如果旧的CPU是一个问题,那么就使用bsr。对我来说,这意味着没有编译器标志来控制这一点,相反,他们建议手动识别__cpu,然后调用bsrlzcnt

简而言之,MSVC不支持不同的CPU架构(除了x86/64/ARM)。

没有办法专门针对Haswell,但有一种方法可以假设AVX2可用性(/arch:AVX2),它还假设tzcnt可用性和其他BMI1和BMI2指令,以及FMA3。还有一种方法可以针对广泛的体系结构进行调整(/favor选项;不幸的是,只有三个广泛的组可用:通用Intel、通用AMD和Intel Atom)。

_BitScanReverse没有理由不在/arch:AVX2下生成tzcnt,除了错过了优化机会例如,_mm_set1_*内部函数在不同的/arch选项下生成不同的代码。即使是显式命名的内部函数也不总是用于生成它们所暗示的指令。类似_mm_load_si128可以是无运算(使用内存操作数而不是寄存器与后续运算融合)。或者参数为零的CCD_ 34可能只是CCD_。

我已经创建了开发者社区关于这个遗漏优化的问题。目前,您可以使用默认情况下执行运行时检测的<bit>函数,但无条件地将lzcnt/tzcnt/arch:AVX2一起使用。

(在优化的构建中,几乎没有办法避免将AVX2和BMI指令与/arch:AVX2一起使用,因为总是有一些自动向量化的空间,所以_BitScanReverse无论如何都不能"移植到/arch:AVX2及以上版本的遗留处理器")

[没有完全回答标题中的问题,而是试图解决OP的问题]

如果你能负担得起一个相当新的编译器,并且可以指定/std:c++20)-你可以使用标准的c++std::countl_zero/std::count l_one/std::country_zero/std::country_one;有关详细信息,请参阅https://en.cppreference.com/w/cpp/header/bit。

根据个人经验:这些恰好在3个不同的平台和3个不同编译器上运行得很有魅力。

正如我在上面发布的,MSVC似乎不支持不同的CPU架构(除了x86/64/ARM之外)。

这篇文章说:;在不支持lzcnt指令的英特尔处理器上,指令字节编码执行为bsr(位扫描反向)。如果代码的可移植性是一个问题,那么考虑使用_BitScanReverse内部">

这篇文章建议,如果旧的CPU是一个问题,那么就使用bsr。对我来说,这意味着没有编译器标志来控制这一点,相反,他们建议手动识别__cpuid,然后根据结果调用bsrlzcnt

更新

正如@dewaffled所指出的,x64内部列表中确实存在_tzcnt_u32/_tzcnt_u64

通过查看窗格左侧按字母顺序列出的内部函数,我被误导了。我想知道";内在论";以及";内在函数";,即CCD_ 52是固有函数而不是固有函数。

最新更新