VS:_bitscanreverse64固有的意外优化行为
VS: unexpected optimization behavior with _BitScanReverse64 intrinsic
以下代码在调试模式下工作正常,因为_bitscanreverse64已定义如果没有设置,则返回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社区2015,版本14.0.25431.01 Update 3.(我留下了COUT,因此在优化期间不会删除变量索引)。还有有效的解决方法,还是我不应该直接使用此编译器?
afaict,当输入为零时,固有的叶子垃圾在 index
中的垃圾,比ASM指令的行为弱。这就是为什么它具有单独的布尔返回值和整数输出操作数。
尽管index
ARG通过引用进行,但编译器将其视为仅输出。
unsigned char _BitScanReverse64 (unsigned __int32* index, unsigned __int64 mask)
英特尔的内在指南文档的同一内在文档似乎比您链接的Microsoft文档更清晰,并阐明了MS文档试图说的话。但是,经过仔细阅读,他们似乎都说同一件事,并描述了bsr
指令周围的薄包装。
Intel记录 AMD的BSF条目在 AMD64体系结构中 程序员的手册 第3卷: 通用和 系统说明 ...如果第二操作数包含0,则指令将ZF设置 至1,不更改目标寄存器的内容。... 在当前英特尔硬件上,实际行为与AMD的文档匹配:当SRC操作数为0时,它将目的地寄存器未修改。也许这就是为什么MS将其描述为仅在输入为非零时设置 在英特尔(但也许不是AMD)上,这甚至没有将64位寄存器截断为32位。例如 idk为什么英特尔仍然没有记录下来。也许是一个真正旧的x86 cpu(像原始386?)一样,以不同的方式实现了它?Intel和AMD经常超越X86手册中记录的内容,以免打破现有的广泛使用的代码(例如Windows),这可能就是这样开始的。 在这一点上,英特尔似乎不太可能丢弃该输出依赖性并留下实际垃圾或输入= 0的-1或32,但是缺乏文档使该选项打开。 Skylake删除了 当然,由于 MSVC优化了您的 因此,在C 术语中,固有的固有依赖性对 不幸的是,在英特尔硬件上,BSR
指令在输入为0时产生"未定义值",但在这种情况下设置ZF。Index
(以及Interinsic's的Interinsic's返回值非零)。mov rax,-1
;bsf eax, ecx
(带有零ECX)留下RAX = -1(64位),而不是您从xor eax, 0
获得的0x00000000ffffffff
。但是对于非零ECX,bsf eax, ecx
具有零扩展到RAX的通常效果,例如RAX = 3。lzcnt
和tzcnt
的错误依赖性(后来的Uarch丢弃了popcnt
的False DEP),同时仍保留bsr
/bsf
的依赖关系。(为什么要打破" lzcnt的输出依赖性"?)index = 0
初始化,大概它只是使用所需的任何目的地寄存器,而不一定是持有C变量的先前值的寄存器。,即使您想要对于,我认为即使在AMD上保证它也可以利用DST不变的行为。index
。但是在ASM中,指令 do 在DST寄存器上具有输入依赖性,例如add dst, src
指令。如果编译器不小心,这可能会导致意外的性能问题。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)
}
如果要避免使用此额外的检查分支,则可以通过不同的内在用途使用lzcnt
指令(例如Intel Haswell或AMD Bulldozer IIRC)。即使输入为全零,它也会"工作",并且实际上计算领导零,而不是返回最高集合位的索引。
- 空基优化子对象的地址
- 在C++中对T*类型执行std::move的意外行为
- 关闭||运算符优化
- 如何解决gcc编译器优化导致的centos双编译器设置中的分段错误
- 使用取消引用的指针的多态性会产生意外的结果.为什么?
- 处理除以零会导致<csignal>意外行为
- 返回值优化:显式移动还是隐式
- 人脸跟踪arduino代码的优化
- 使用仅使用一次的变量调用的复制构造函数.这可能是通过调用move构造函数进行编译器优化的情况吗
- vscode下的Arduino代码出现意外编译错误
- 使用++运算符会导致意外的结果
- 套接字读取后,我在缓冲区中看到意外输入
- 更改.cpp程序的输入文件中数据的位置会意外更改输出
- 纯函数,为什么没有优化
- 为什么大多数 pair 实现默认不使用压缩(空基优化)?
- 如何以优化的方式同时迭代两个间距不相等的数组
- 使用vscode调试时,GDB意外退出
- VS:_bitscanreverse64固有的意外优化行为
- 使用clang优化编译时得到意外结果
- GCC优化导致意外返回值