我有一个程序,它大量使用内部命令_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
是否也是如此 - 它解释了(正如我所知)
bsf
与tzcnt
不同,但是MSVC似乎没有检查输入==0
这增加了问题:为什么bsf
适用于MSVC?
更新
好吧,这很简单,我实际上为MSVC打了_BitScanForward
。Doh!
更新
所以我在这里添加了一些不必要的混乱。理想情况下,我想使用一个内在的__tzcnt
,但它在MSVC中不存在,所以我使用_BitScanForward
加上一个额外的检查来解释0
输入。
然而,MSVC支持LZCNT,我也有类似的问题(但在我的代码中使用较少)。
稍微更新一下的问题是:MSVC如何处理LZCNT(而不是TZCNT)?
答案:请参阅此处。具体地说:";在不支持lzcnt
指令的英特尔处理器上,指令字节编码执行为bsr
(反向位扫描)。如果代码的可移植性是一个问题,那么考虑使用_BitScanReverse
内在">
这篇文章建议,如果旧的CPU是一个问题,那么就使用bsr
。对我来说,这意味着没有编译器标志来控制这一点,相反,他们建议手动识别__cpu
,然后调用bsr
或lzcnt
。
简而言之,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
,然后根据结果调用bsr
或lzcnt
。
更新
正如@dewaffled所指出的,x64内部列表中确实存在_tzcnt_u32
/_tzcnt_u64
。
通过查看窗格左侧按字母顺序列出的内部函数,我被误导了。我想知道";内在论";以及";内在函数";,即CCD_ 52是固有函数而不是固有函数。