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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了VS:_BitScanReverse64内在的意外优化行为相关的知识,希望对你有一定的参考价值。

以下代码在调试模式下工作正常,因为如果没有设置Bit,_BitScanReverse64被定义为返回0。 Citing 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指令的行为。这就是为什么它有一个单独的布尔返回值和整数输出操作数。


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

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

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

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 = 0初始化,可能它只使用它想要的任何目标寄存器,不一定是保存C变量的先前值的寄存器。所以即使你想这样做,我也不认为你可以利用dst未修改的行为,即使它在AMD上有所保证。

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

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


您需要检查它以确保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)
}

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

以上是关于VS:_BitScanReverse64内在的意外优化行为的主要内容,如果未能解决你的问题,请参考以下文章

编译错误的含义:错误 C2704:__va_start 内在函数只允许在可变参数中使用?

英特尔 SIMD 内在函数:_mm256_i64scatter_pd

64 位特定 simd 内在

在 GCC 10.3.0 中找不到 _mm256_rem_epu64 内在函数

int64_t 指针转换为 AVX2 内在 _m256i

llvm中内在的cvta_shared_yes、cvta_shared_yes_64、cvta_to_shared_yes_64等的目的是啥