繁体   English   中英

VS:_BitScanReverse64固有的意外优化行为

[英]VS: unexpected optimization behavior with _BitScanReverse64 intrinsic

以下代码在调试模式下可以正常工作,因为_BitScanReverse64被定义为在未设置Bit的情况下返回0。 引用MSDN :(返回值为)“如果设置了索引,则为非零;如果未找到设置位,则为0”。

如果我以发布模式编译此代码,它仍然可以工作,但是如果启用了编译器优化(例如\\ O1或\\ O2),则索引不为零,并且assert()失败。

#include <iostream>
#include <cassert>

using namespace std;

int main()
{
  unsigned long index = 0;
  _BitScanReverse64(&index, 0x0ull);

  cout << index << endl;

  assert(index == 0);

  return 0;
}

这是预期的行为吗? 我正在使用Visual Studio Community 2015,版本14.0.25431.01更新3。(我留了cout,以便在优化过程中不删除变量索引)。 还有一种有效的解决方法,还是我不应该直接使用此内在编译器?

AFAICT, 当输入为零时内在函数在index留下垃圾,比asm指令的行为弱。 这就是为什么它具有单独的布尔返回值和整数输出操作数的原因。

尽管index arg被引用,编译器仍将其视为仅输出。


unsigned char _BitScanReverse64 (unsigned __int32* index, unsigned __int64 mask)
英特尔针对相同内在函数的内在函数指南文档似乎比您链接的Microsoft文档更清晰,并且为MS文档试图说的内容提供了一些启示。 但是仔细阅读后,他们似乎都说了同样的话,并在bsr指令周围描述了一个薄包装。

当输入为0时, Intel将BSR指令记录为产生“未定义的值”,但在这种情况下设置ZF。 但是,AMD记录为目标不变:

AMD的BSFAMD64体系结构程序员手册第3卷:通用和系统说明中的 条目

...如果第二个操作数包含0,则指令将ZF设置为1,并且不更改目标寄存器的内容。 ...

在当前的Intel硬件上,实际行为与AMD的文档相符:当src操作数为0时,目标寄存器保持不变。这也许就是为什么MS将其描述为仅在输入为非零时设置Index (并且内部函数的返回值为非零)。

在Intel( 但可能不是AMD )上,这甚至不会将64位寄存器截断为32位。 例如mov rax,-1 ; bsf eax, ecx (ECX为零)离开RAX = -1(64位),而不是从xor eax, 0获得的0x00000000ffffffff 但是对于非零ECX, bsf eax, ecx具有零扩展到RAX的通常效果,例如留下RAX = 3。


IDK为什么英特尔仍未对此进行记录。 也许真正的旧x86 CPU(例如原始386?)以不同的方式实现它? 英特尔和AMD经常超越x86手册中记录的内容,以免破坏现有的广泛使用的代码(例如Windows) ,这可能就是这样开始的。

在这一点上,英特尔似乎不太可能会放弃对输出的依赖性,并让实际的垃圾或输入== 0时为-1或32,但是缺少文档使该选项处于打开状态。

SKYLAKE微架构放弃了假依赖于lzcnttzcnt (和稍后的uarch下降虚假DEP的popcnt ),同时仍然保留依赖bsr / bsf 为什么破坏LZCNT的“输出依赖性”很重要?


当然,由于MSVC优化了index = 0初始化,因此大概只使用它想要的任何目标寄存器,而不必使用保存C变量先前值的寄存器。 因此,即使您愿意,我也不认为您可以利用未经修改的dst行为,即使AMD对此有保证。

因此,以C ++术语来说,内在函数对index没有输入依赖性 但是在asm中,该指令确实对dst寄存器具有输入依赖性,就像add dst, src指令一样。 如果编译器不小心,可能会导致意外的性能问题。

不幸的是,在Intel硬件 ,即使结果从不依赖于popcnt / lzcnt / tzcnt asm指令,它们也对目标地址有错误的依赖 不过,编译器现在已经知道了,因此可以解决此问题,因此,在使用内部函数时,您不必担心它(除非您拥有超过两年的编译器,因为它是最近才发现的)。


您需要检查它以确保index有效,除非您知道输入为非零。 例如

if(_BitScanReverse64(&idx, input)) {
    // idx is valid.
    // (MS docs say "Index was set")
} else {
    // input was zero, idx holds garbage.
    // (MS docs don't say Index was even set)
    idx = -1;     // might make sense, one lower than the result for bsr(1)
}

如果要避免执行此额外的检查分支,那么如果您要瞄准足够新的硬件(例如Intel Haswell或AMD Bulldozer IIRC),则可以通过不同的内在函数使用lzcnt指令 即使输入全零,它也“起作用”,并且实际上计数前导零而不是返回最高设置位的索引。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM