我有一个程序,大量使用内部命令
_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 将
tzcnt
与 haswell/bmi2 一起使用,否则诉诸 rep bsf
。
MSVC 使用 bsf
而不使用 rep
。
我还发现了这个有用的答案,其中指出:
bsf
是否也是如此?bsf
与tzcnt
不同,但是MSVC似乎不会检查输入== 0这增加了问题:为什么
bsf
适用于 MSVC?
更新
好吧,这很简单,我实际上为 MSVC 打电话
_BitScanForward
。嚯!
更新
所以我在这里添加了一些不必要的混乱。理想情况下,我想使用内在的
__tzcnt
,但这在 MSVC 中不存在,所以我求助于 _BitScanForward
加上额外的检查来解释 0
输入。
但是,MSVC 支持 LZCNT,我也有类似的问题(但在我的代码中使用较少)。
稍微更新的问题是:MSVC 如何处理 LZCNT(而不是 TZCNT)?
答案:参见这里。具体来说:“在不支持
lzcnt
指令的英特尔处理器上,指令字节编码作为 bsr
(位扫描反向)执行。如果考虑代码可移植性,请考虑使用 _BitScanReverse
内在函数。 ”
本文建议如果担心较旧的 CPU,请使用
bsr
。对我来说,这意味着没有编译器标志来控制这一点,相反,他们建议手动识别 __cpu
,然后调用 bsr
或 lzcnt
。
简而言之,MSVC 不支持不同的 CPU 架构(x86/64/ARM 之外)。
正如我上面发布的,MSVC 似乎不支持不同的 CPU 架构(x86/64/ARM 之外)。
本文说:“在不支持
lzcnt
指令的 Intel 处理器上,指令字节编码以 bsr
(位扫描反向)执行。如果考虑代码可移植性,请考虑使用 _BitScanReverse
是内在的。”
本文建议如果担心较旧的 CPU,请使用
bsr
。对我来说,这意味着没有编译器标志来控制这一点,相反,他们建议手动识别 __cpuid
,然后根据结果调用 bsr
或 lzcnt
。
更新
正如 @dewaffled 指出的,
x64 内在函数列表中确实有
_tzcnt_u32
/
_tzcnt_u64
。
我通过查看窗格左侧的按字母顺序排列的内部函数列表而受到误导。我想知道“内在函数”和“内在函数”之间是否有区别,即
_tzcnt_u64
是内在函数,但不是内在函数。
如果您能负担得起一个相当新的编译器并且可以指定 /std:c++20) - 您可以使用标准 C++ std::countl_zero / std::countl_one / std::countr_zero / std::countr_one ;详情请参考 https://en.cppreference.com/w/cpp/header/bit .
从个人经验来看:这些恰好在 3 个不同的平台和 3 个不同的编译器上发挥作用。