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
在 GCC 10.3.0 中找不到 _mm256_rem_epu64 内在函数
llvm中内在的cvta_shared_yes、cvta_shared_yes_64、cvta_to_shared_yes_64等的目的是啥