在测量某些东西时,我测量的吞吐量比我计算的要低得多,我将其缩小到LZCNT指令(TZCNT也会发生这种情况),如以下基准所示:
xor ecx, ecx
_benchloop:
lzcnt eax, edx
add ecx, 1
jnz _benchloop
和
xor ecx, ecx
_benchloop:
xor eax, eax ; this shouldn't help, but it does
lzcnt eax, edx
add ecx, 1
jnz _benchloop
第二个版本要快得多。它不应该。 LZCNT没有理由对其输出有输入依赖性。与BSR / BSF不同,xZCNT指令始终覆盖其输出。
我在4770K上运行它,因此LZCNT和TZCNT没有被执行为BSR / BSF。
这里发生了什么?
答案 0 :(得分:11)
这仅仅是您的Intel Haswell CPU和之前的几个 1 CPU的微架构的限制。从Skylake-S(客户端)开始,tzcnt
和lzcnt
已修复此问题,但问题仍然存在于popcnt
,直到在Cannon Lake中修复为止。
在这些微架构上,tzcnt
,lzcnt
和popcnt
的目标操作数被视为输入依赖,即使在语义上它不是。现在我怀疑这是一个真正的错误"如果它只是一个意想不到的行为/疏忽,我预计它将被修复,因为它已经发布的几个新的微架构之一引入的。
更有可能的是,这是基于以下两个因素中的一个或两个的设计折衷:
popcnt
,lzcnt
和tzcnt
的硬件为likely all shared,其中包含现有bsf
和bsr
条款。现在bsf
和bsr
做依赖于以前的目标值在实践 2 中的特殊情况全比特零输入,因为英特尔芯片在这种情况下不会修改目的地。因此,组合硬件的最简单设计完全有可能导致在同一单元上执行的其他类似指令继承相同的依赖性。
绝大多数x86两个操作数ALU指令都依赖于目标操作数,因为它也用作源。这三个受影响的指令有点独特,因为它们是一元运算符,但与现有的一元运算符(如not
和neg
不同,它们有一个操作数用作源和目标)具有不同的源和目标操作数,使它们表面上与大多数2输入指令相似。也许重命名器/调度器电路不能区分这些一元二寄存器操作数与绝大多数没有这种依赖性的普通共享源/目标二输入指令的特殊情况。
事实上,对于popcnt
的情况,英特尔发布了各种勘误表,涵盖了错误的依赖性问题,例如Haswell Desktop的HSD146和Skylake的SKL029,其中包括:
POPCNT指令可能需要更长时间才能执行
问题使用32或64位操作数执行POPCNT指令可能是 延迟到之前的非独立指令已执行。
含义使用POPCNT指令的软件可能会遇到低于预期的性能。
解决方法无识别
我总是发现这个错误是不寻常的,因为它并没有真正识别任何类型的功能缺陷或不符合规范,这基本上是所有其他勘误的情况。英特尔并没有真正记录OoO执行引擎的特定性能模型,而且还有很多其他性能问题"陷阱"这些年来出现和消失的很多(许多影响很大,这个非常小的问题)在勘误中没有记录。不过,这或许可以提供一些证据,证明它可以被视为一个错误。奇怪的是,该错误从未扩展到包括tzcnt
或lzcnt
,它们在引入时具有相同的问题。
1 好tzcnt
和lzcnt
只出现在Haswell中,但问题也存在于popcnt
中,这是在Nehalem中引入的 - 但是错误依赖Sandy Bridge或更高版本的问题perhaps only exists。
2 在实践中,虽然没有在ISA文档中记录,但因为全英零手册输入的结果未在英特尔手册中定义。然而,大多数或所有英特尔芯片都实现了这种行为,因为在这种情况下保持目标寄存器不变。
答案 1 :(得分:3)
按照@BrettHale建议的方式,你可能(如果奇怪的话)你正在击中角落部分标志更新档位。标志状态应该在理论上简单地重命名,因为以下添加更新所有标志,但如果它不是由于某种原因那么它将引入循环携带依赖,并且插入xor将破坏该依赖。
很难确定这是否正在发生,但它看起来是偶然的一瞥是最可能的解释;您可以通过将xor
替换为test
来测试该假设(这也会破坏标志依赖性,但对寄存器依赖性没有影响)。