为啥 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 你写了错误的指令,但你写的似乎也适用于TZCNT
和BSF
。
是的,抱歉,我很快扫了一眼这个问题。正如您所发现的,同样的事情适用于 TZCNT 和 BSF。
“好”消息是对于定义了bsf
的所有值,tzcnt
至少与bsf
一致。它们仅在零输入的行为上有所不同,其中bsf
未定义,tzcnt
返回 32 或 64(分别针对 32 位或 64 位输入)。手上的lzcnt
返回完全不同的结果(本质上是31 - bsr
)。
【参考方案1】:
Sandy Bridge(和更早的)处理器似乎支持lzcnt
和tzcnt
的原因是这两个指令都具有向后兼容的编码。
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
至少 bsf
和 tzcnt
在源 0 时生成相同的输出。bsr
和 lzcnt
不同意这一点。
此外,lzcnt
和 tzcnt
的执行速度也比 bsr
/bsf
快得多。bsf
和 tzcnt
不能就标志的使用达成一致,这完全糟透了。
这种不必要的不一致意味着我不能使用tzcnt
作为bsf
的替代品,除非我可以确定它的来源是非零的。
【讨论】:
只有 AMD CPU 比bsr
/bsf
更快地执行 lzcnt/tzcnt。 (此外,英特尔 Skylake 使 lz/tzcnt 没有输出依赖,他们在以前的 uarches 上这样做了。popcnt
仍然对 Skylake 有输出依赖。)BSR/BSF 总是对输出有依赖,因为它们使输出保持不变对于输入 = 0。 (AMD 记录了这种行为,英特尔实现了它,但在他们的手册中说“未定义”)。
在您知道输入非零的任何情况下,您仍然可以使用tzcnt
作为替代品,而不知道它是否会解码为tzcnt
或bsf
。编译器实际上是这样做的。
我的猜测是 AMD 无法根据单个 uop 的 input 设置标志。也许它处理 ZF 的方式是硬连线地根据 ALU uops 的结果设置它,因此需要根据输入设置它的 BSF/BSR 只是不兼容。 (这可能是他们在定义lzcnt
/tzcnt
时使标志结果不兼容的原因,因为我认为是 AMD 首先介绍了它们。)我同意如果tzcnt
设置标志与bsf
,因此当它可能以 bsf
运行时,您可以在更多情况下使用它,但这可能会阻止 AMD 让它变得更快。以上是关于为啥 TZCNT 适用于我的 Sandy Bridge 处理器?的主要内容,如果未能解决你的问题,请参考以下文章
AVX512BW:使用bsf / tzcnt处理32位代码中的64位掩码?
centos系统查看cpu,为啥是显示 Intel Xeon E312xx (Sandy Bridge)