当前位置: 首页 > 知识库问答 >
问题:

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

鲁成天
2023-03-14

以下代码在调试模式下工作良好,因为_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 Community 2015,版本14.0.25431.01更新3。(我把cout留在了,这样在优化过程中就不会删除变量index)。另外,是否有一个有效的解决办法,或者我应该不直接使用这个编译器?

共有1个答案

禹昆
2023-03-14

另外,当输入为零时,本征将垃圾留在index中,比asm指令的行为弱。这就是它有一个单独的布尔返回值和整数输出操作数的原因。

尽管index参数是通过引用获得的,但编译器将其视为仅输出。

unsigned char_bitscanreverse64(unsigned__int32*索引,unsigned__int64掩码)
Intel针对同一内部文件的内部文件指南文档似乎比您链接的Microsoft文档更清晰,并对MS文档试图表达的内容提供了一些信息。但仔细阅读,它们似乎都在说同样的话,并且描述了bsr指令的一个薄包装。

Intel将bsr指令记录为在输入为0时生成“未定义值”,但在这种情况下设置ZF。但AMD将其记录为保持目标不变:

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

...如果第二个操作数包含0,则指令集将ZF设置为1,并且不改变目标寄存器的内容。...

在Intel(但可能不是AMD)上,这甚至没有将64位寄存器截短为32位。例如mov rax,-1BSF eax,ECX(带有零ECX)保留RAX=-1(64位),而不是从异或eax,0得到的0x00000000FFFFFFFFF。但对于非零ECX,bsf eax,ECX具有通常的效应,即将零扩展为RAX,例如,留下RAX=3。

为什么英特尔还没有记录下来。可能是一个真正老旧的x86 CPU(像原来的386?)以不同的方式实现?Intel和AMD经常超越x86手册中的规定,以避免破坏现有的广泛使用的代码(例如Windows),这可能是开始的原因。

在这一点上,Intel似乎不太可能放弃输出依赖关系,并为Input=0保留实际的垃圾或-1或32,但由于缺乏文档,这一选项仍然是开放的。

Skylake删除了lzcnttzcnt的false依赖项(后来的一个uarch删除了popcnt的false dep),同时仍然保留了bsr/bsf的依赖项。(为什么打破LZCNT的“输出依赖”很重要?)

当然,由于MSVC优化了index=0初始化,所以它可能只是使用它想要的任何目标寄存器,而不一定是保存C变量前一个值的寄存器。所以即使你想,我不认为你可以利用DST未修改的行为,即使它是在AMD上保证。

所以用C++的术语来说,intrinsic不依赖于index。但在asm中,指令对dst寄存器有输入依赖关系,如add dst,src指令。如果编译器不小心,这可能会导致意外的性能问题。

不幸的是,在Intel硬件上,popcnt/lzcnt/tzcntasm指令对它们的目的地也有错误的依赖关系,尽管结果从不依赖于它。编译器可以解决这一问题,因为现在已经知道了,所以在使用本机时不必担心这一点(除非您有一个超过几年的编译器,因为它是最近才发现的)。

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

如果您想避免这个额外的检查分支,那么如果您针对的是足够新的硬件(例如Intel Haswell或AMD推土机IIRC),您可以通过不同的内部控制使用lzcnt指令。它“工作”甚至当输入是全零,实际上计数前导零,而不是返回最高设置位的索引。

 类似资料:
  • 我试图在函数的管道中使用中的函数,并发现它对我的语法选择非常敏感,这是我没有想到的。这里有一个玩具的例子。 好的,现在让我们假设我想在数据帧中选定列的名称上迭代一个函数(幽默一下)。在这里,我将使用选定列中的值来过滤初始数据集,计算剩余的唯一ID的数量,并将结果作为一行tibble返回,然后将其绑定到新的tibble中。当我在函数中创建一个新的tibble,然后将应用于该tibble中的选定列作为

  • 问题内容: 这就是整个查询… 如果… 和… 有明显的理由吗? 正在服用? 扩展说明 问题答案: 您可以始终使用EXPLAIN或EXPLAIN EXTENDED 来查看MySql对查询所做的操作 您也可以用稍微不同的方式编写查询,是否尝试过以下方法? 看看效果如何会很有趣。我希望它会更快,因为目前,我认为MySql将为您拥有的每个节目运行内部查询1(这样一个查询将运行多次。联接应该更有效。) 如果希

  • 我有一张带有以下原型的桌子: 我想创建一个过程来插入具有特定长度的随机字符串,我写了以下内容: 这样说: 问题是它插入NULL作为NAME字段。 我的怀疑是,当同时循环终止时,对所做的更改超出了范围。 我还尝试使用如下的全局变量: 也在程序之外声明了,但没有发生什么特别的事情 感谢您的帮助<向你问好。

  • 问题内容: 前一阵子,在使用Class.getMethod和自动装箱时,我遇到了类似的问题,因此在自己的查找算法中实现它很有意义。但是,真正让我感到困惑的是,以下两种方法也不起作用: String.class实现了Serializable接口,我确实希望它包含在lookup方法中。 我也必须在自己的查找算法中考虑这一点吗? 编辑 :我确实读过Javadoc, 所以让我强调一下问题的第二部分 :如果

  • 我是Python的新手,我对Python处理空对象的方式有点困惑。 考虑这段代码; 我得到了这段代码的以下输出。 我假设由{}初始化的对象是有效对象。为什么Python不那样对待它?为什么要得到diff If条件的diff输出? 在C++中,当我说 如果obj不是NULL,它将进入IF块(不管它是垃圾值还是其他) 但当我翻译成Python时也是一样的。 为什么?我读到Python将{}计算为fal