为什么破坏LZCNT的"输出依赖性"很重要?

har*_*old 22 x86 assembly

在测量某些东西的同时,我测量的吞吐量比我计算的要低得多,我将其缩小到LZCNT指令(它也发生在TZCNT中),如以下基准所示:

  xor ecx, ecx
_benchloop:
  lzcnt eax, edx
  add ecx, 1
  jnz _benchloop
Run Code Online (Sandbox Code Playgroud)

和:

  xor ecx, ecx
_benchloop:
  xor eax, eax  ; this shouldn't help, but it does
  lzcnt eax, edx
  add ecx, 1
  jnz _benchloop
Run Code Online (Sandbox Code Playgroud)

第二个版本要快得多.它不应该.LZCNT没有理由对其输出有输入依赖性.与BSR/BSF不同,xZCNT指令总是覆盖其输出.

我在4770K上运行它,所以LZCNT和TZCNT没有被执行为BSR/BSF.

这里发生了什么?

Bee*_*ope 14

这简直是在您的英特尔Haswell的CPU和几个以前的微架构的限制,1个CPU.它已被固定tzcnt,并lzcnt为SKYLAKE微架构-S(客户端),但问题仍然是popcnt,直到它被固定在炮湖.

在那些微架构上,目标操作数tzcnt,lzcnt并且popcnt被视为输入依赖,即使在语义上它不是.现在我怀疑这是一个真正的"错误":如果它只是一个意想不到的行为/疏忽,我预计它将在自引入以来已经发布的几个新微架构中的一个中得到修复.

更可能的是,它是基于以下两个因素中的一个或两个的设计折衷:

  • 硬件popcnt,lzcnt并且tzcnt可能与现有bsfbsr指令共享.对于全比特零输入的特殊情况,现在bsfbsr 确实依赖于实践2中的先前目标值,因为在这种情况下英特尔芯片保持目的地未被修改.因此,组合硬件的最简单设计完全有可能导致在同一单元上执行的其他类似指令继承相同的依赖关系.

  • 绝大多数x86两个操作数ALU指令都依赖于目标操作数,因为它也用作源.这三个受影响的指令有点独特,因为它们是一元运算符,但与现有的一元运算符不同not,neg它们有一个操作数用作源和目标,它们具有不同的源和目标操作数,使它们表面上与大多数2输入指令相似.也许重命名器/调度器电路不区分这些一元二寄存器操作数与绝大多数没有这种依赖性的普通共享源/目标二输入指令的特殊情况.

事实上,为的情况下,popcnt英特尔已经发布的各种勘误表涵盖了虚假的依赖问题,如HSD146为Haswell的桌面和SKL029为SKYLAKE微架构,其内容如下:

POPCNT指令可能需要更长时间才能执行

问题使用32或64位操作数执行POPCNT指令可能会延迟,直到执行了以前的非依赖指令.

使用POPCNT指令的蕴涵软件可能会遇到低于预期的性能.

解决方法未确定

我总是发现这个错误是不寻常的,因为它并没有真正识别任何类型的功能缺陷或不符合规范,这基本上是所有其他勘误表的情况.英特尔并没有真正记录OoO执行引擎的特定性能模型,并且有很多其他性能"陷阱"多年来出现并消失(许多对这个非常小的问题影响更大)在勘误表中记录.不过,这或许可以提供一些证据,证明它可以被视为一个错误.奇怪的是,这些错误从未延伸到包含tzcntlzcnt在引入时具有相同的问题.


1好吧tzcnt,lzcnt只出现在Haswell,但问题也存在popcnt于Nehalem中 - 但假依赖问题可能仅存在于 Sandy Bridge或更高版本.

2 实际上,尽管未在ISA文档中记录,但由于英特尔手册中未定义全零输入的结果.然而,大多数或所有英特尔芯片实现了这种行为,因为在这种情况下保持目标寄存器不变.