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

VS: unexpected optimization behavior with _BitScanReverse64 intrinsic

本文关键字:意外 优化 bitscanreverse64 VS      更新时间:2023-10-16

以下代码在调试模式下工作正常,因为_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记录BSR指令在输入为0时产生"未定义值",但在这种情况下设置ZF。

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

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

在当前英特尔硬件上,实际行为与AMD的文档匹配:当SRC操作数为0时,它将目的地寄存器未修改。也许这就是为什么MS将其描述为仅在输入为非零时设置Index(以及Interinsic's的Interinsic's返回值非零)。

在英特尔(但也许不是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?)一样,以不同的方式实现了它?Intel和AMD经常超越X86手册中记录的内容,以免打破现有的广泛使用的代码(例如Windows),这可能就是这样开始的。

在这一点上,英特尔似乎不太可能丢弃该输出依赖性并留下实际垃圾或输入= 0的-1或32,但是缺乏文档使该选项打开。

Skylake删除了lzcnttzcnt的错误依赖性(后来的Uarch丢弃了popcnt的False DEP),同时仍保留bsr/bsf的依赖关系。(为什么要打破" lzcnt的输出依赖性"?)


当然,由于 MSVC优化了您的index = 0初始化,大概它只是使用所需的任何目的地寄存器,而不一定是持有C变量的先前值的寄存器。,即使您想要对于,我认为即使在AMD上保证它也可以利用DST不变的行为。

因此,在C 术语中,固有的固有依赖性对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)。即使输入为全零,它也会"工作",并且实际上计算领导零,而不是返回最高集合位的索引。