VS:_BitScanReverse64内在的意外优化行为

时间:2016-12-27 20:08:05

标签: c++ visual-studio optimization intrinsics

以下代码在调试模式下工作正常,因为定义了_BitScanReverse64 如果没有设置Bit,则返回0。 Citing MSDN: (返回值为)"如果设置了索引则为非零;如果未找到设置位,则为0。"

如果我在发布模式下编译此代码它仍然有效,但如果我启用编译器 优化,例如\ O1或\ O2,索引不为零,blit=False失败。

var emotes =
  ("best cry hello pain smile").split(" ");

for(var index = 0, emote; index < emotes.length; index++) {
  emote = RegExp(("\\b\\s*(\\+" + emotes[index] + ")\\s*\\b"), "gi");
  chat = chat.replace(emote, '<img src="./img/emotes/$1.png" class="chat">');
}

这是预期的行为吗?我正在使用Visual Studio Community 2015,版本14.0.25431.01更新3.(我离开了cout,因此在优化期间不会删除变量索引)。还有一个有效的解决方法,还是我不应该直接使用这个编译器内在函数?

1 个答案:

答案 0 :(得分:8)

AFAICT,当输入为零时弱于asm指令的行为。这就是为什么它有一个单独的布尔返回值和整数输出操作数。

index
Intel's intrinsics guide documentation for the same intrinsic似乎比您链接的Microsoft docs更清晰,并且阐明了MS文档试图说的内容。但仔细阅读后,他们似乎都表示同样的事情,并在unsigned char _BitScanReverse64 (unsigned __int32* index, unsigned __int64 mask)指令周围描述一个薄的包装。

Intel documents the BSR instruction在输入为0时产生“未定义的值”,但在这种情况下设置ZF。但AMD将其记录为保持目的地不变

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

IDK为何英特尔仍然没有记录它。也许一个非常古老的x86 CPU(就像原来的386?)以不同的方式实现它?英特尔和AMD经常go above and beyond what's documented in the x86 manuals in order to not break existing code (e.g. Windows),这可能就是这个问题的起源。在这一点上,它们似乎不太可能丢弃输出依赖性并且实际上是垃圾,或者输入= 0或者是-1或32,但缺少文档会使该选项打开。

当然,由于 MSVC优化了你的Index初始化,大概它只是使用它想要的任何目标寄存器,不一定是保存C变量的先前值的寄存器。所以即使你想这样做,我也不认为你可以利用dst未修改的行为,即使它在AMD上得到保证。

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

不幸的是,在英特尔硬件上popcnt / lzcnt / tzcnt asm instructions also have a false dependency on their destination,即使结果从不依赖于它。编译器现在解决这个问题,因为它已经知道,因此在使用内在函数时你不必担心它(除非你有一个超过几年的编译器,因为它最近才被发现)。

您需要检查它以确保add dst, src有效,除非您知道输入非零。 e.g。

index

如果您想避免这种额外的支票分支,如果您的目标是足够新的硬件(例如Intel Haswell或AMD Bulldozer IIRC),您可以通过不同的内在函数使用lzcnt instruction。即使输入全为零,它也“有效”,实际上计数前导零而不是返回最高设置位的索引。