为啥 TZCNT 适用于我的 Sandy Bridge 处理器?

Posted

技术标签:

【中文标题】为啥 TZCNT 适用于我的 Sandy Bridge 处理器?【英文标题】:Why does TZCNT work for my Sandy Bridge processor?为什么 TZCNT 适用于我的 Sandy Bridge 处理器? 【发布时间】:2017-05-09 21:34:28 【问题描述】:

我正在运行属于 Sandy Bridge 微架构的 Core i7 3930k。 执行以下代码时(在MSVC19、VS2015下编译),结果让我吃惊(见cmets):

int wmain(int argc, wchar_t* argv[])

    uint64_t r = 0b1110'0000'0000'0000ULL;
    uint64_t tzcnt = _tzcnt_u64(r);
    cout << tzcnt << endl; // prints 13

    int info[4];
    __cpuidex(info, 7, 0);
    int ebx = info[1];
    cout << bitset<32>(ebx) << endl; // prints 32 zeros (including the bmi1 bit)

    return 0;

反汇编表明tzcnt指令确实是从内在发出的:

    uint64_t r = 0b1110'0000'0000'0000ULL;
00007FF64B44877F 48 C7 45 08 00 E0 00 00 mov         qword ptr [r],0E000h  
    uint64_t tzcnt = _tzcnt_u64(r);
00007FF64B448787 F3 48 0F BC 45 08    tzcnt       rax,qword ptr [r]  
00007FF64B44878D 48 89 45 28          mov         qword ptr [tzcnt],rax  

为什么我没有收到#UD 无效操作码异常,指令运行正常,CPU 报告它支持上述指令?

这可能是一些奇怪的微代码修订版,其中包含指令的实现但不报告对它的支持(以及 bmi1 中包含的其他内容)?

我还没有检查过bmi1 的其余说明,但我想知道这种现象有多普遍。

【问题讨论】:

来自Instruction Set Reference:LZCNT 与 BSR 不同。例如,当输入操作数为零时,LZCNT 将产生操作数大小。 需要注意的是,在不支持LZCNT的处理器上,指令字节编码是作为BSR执行的。 @Michael Petch 你写了错误的指令,但你写的似乎也适用于TZCNTBSF 是的,抱歉,我很快扫了一眼这个问题。正如您所发现的,同样的事情适用于 TZCNT 和 BSF。 “好”消息是对于定义了bsf 的所有值,tzcnt 至少与bsf 一致。它们仅在零输入的行为上有所不同,其中bsf 未定义,tzcnt 返回 32 或 64(分别针对 32 位或 64 位输入)。手上的lzcnt 返回完全不同的结果(本质上是31 - bsr)。 【参考方案1】:

Sandy Bridge(和更早的)处理器似乎支持lzcnttzcnt 的原因是这两个指令都具有向后兼容的编码。

lzcnt eax,eax  = rep bsr eax,eax
tzcnt eax,eax  = rep bsf eax,eax

在较旧的处理器上,rep 前缀会被忽略。

好消息就这么多。 坏消息是两个版本的语义不同。

lzcnt eax,zero => eax = 32, CF=1, ZF=0  
bsr eax,zero   => eax = undefined, ZF=1
lzcnt eax,0xFFFFFFFF => eax=0, CF=0, ZF=1   //dest=number of msb leading zeros
bsr eax,0xFFFFFFFF => eax=31, ZF=0        //dest = bit index of highest set bit


tzcnt eax,zero => eax = 32, CF=1, ZF=0
bsf eax,zero   => eax = undefined, ZF=1
tzcnt eax,0xFFFFFFFF => eax=0, CF=0, ZF=1   //dest=number of lsb trailing zeros
bsf eax,0xFFFFFFFF => eax=0, ZF=0        //dest = bit index of lowest set bit

至少 bsftzcnt 在源 0 时生成相同的输出。bsrlzcnt 不同意这一点。 此外,lzcnttzcnt 的执行速度也比 bsr/bsf 快得多。bsftzcnt 不能就标志的使用达成一致,这完全糟透了。 这种不必要的不​​一致意味着我不能使用tzcnt 作为bsf 的替代品,除非我可以确定它的来源是非零的。

【讨论】:

只有 AMD CPU 比 bsr/bsf 更快地执行 lzcnt/tzcnt。 (此外,英特尔 Skylake 使 lz/tzcnt 没有输出依赖,他们在以前的 uarches 上这样做了。popcnt 仍然对 Skylake 有输出依赖。)BSR/BSF 总是对输出有依赖,因为它们使输出保持不变对于输入 = 0。 (AMD 记录了这种行为,英特尔实现了它,但在他们的手册中说“未定义”)。 在您知道输入非零的任何情况下,您仍然可以使用tzcnt 作为替代品,而不知道它是否会解码为tzcntbsf。编译器实际上是这样做的。 我的猜测是 AMD 无法根据单个 uop 的 input 设置标志。也许它处理 ZF 的方式是硬连线地根据 ALU uops 的结果设置它,因此需要根据输入设置它的 BSF/BSR 只是不兼容。 (这可能是他们在定义lzcnt/tzcnt 时使标志结果不兼容的原因,因为我认为是 AMD 首先介绍了它们。)我同意如果tzcnt 设置标志与bsf,因此当它可能以 bsf 运行时,您可以在更多情况下使用它,但这可能会阻止 AMD 让它变得更快。

以上是关于为啥 TZCNT 适用于我的 Sandy Bridge 处理器?的主要内容,如果未能解决你的问题,请参考以下文章

使 LINQ 扩展方法适用于我的班级

AVX512BW:使用bsf / tzcnt处理32位代码中的64位掩码?

centos系统查看cpu,为啥是显示 Intel Xeon E312xx (Sandy Bridge)

QTranslator 仅适用于我的程序的某些部分

为啥 setAlpha() 作用于我的所有按钮,而 setImageResource() 只作用于一个按钮?

Jquery Instagram 提要仅适用于我的用户名