TL:DR: 看起来可能是针对这个特定代码序列的一些微体系结构特定调整。没有什么“通常推荐”的东西可以在其他情况下帮助。
经过进一步考虑,我认为@Iwillnotexist Idonotexist的理论最有可能:这是由一个非专业人士编写的,他认为这可能会有所帮助。寄存器分配是一个重要线索:通过选择所有重复使用的寄存器中的低8个,可以避免许多REX前缀。
XORPS在某些英特尔CPU上(Nehalem及更高版本)在“浮点”域中运行,而PXOR始终在“ivec”域中运行。由于将每个ALU输出直接连接到每个ALU输入以进行转发结果将很昂贵,因此CPU设计人员将它们分成不同的域。(转发可节省写回寄存器文件和重新读取的延迟)。域交叉可能需要额外的1个时钟周期(Intel SnB系列)或2个时钟周期(Nehalem)。更多阅读:
What's the difference between logical SSE intrinsics?上我的答案。
我有两个理论:
代码的版权声明显示“2011-2015英特尔”,因此有必要考虑它可能对某些最近的英特尔CPU有所帮助,并不仅仅是基于对英特尔CPU工作方式的误解。 Nehalem是第一个包含PCLMULQDQ的CPU,这是英特尔的产品,如果有什么问题它将被调整以在AMD CPU上表现不佳。代码历史记录不在git repo中,只有添加当前版本的May 6th提交。
Intel白皮书(2009年12月)只使用了PXOR而没有使用XORPS来实现2x pclmul / 2x xor块。
Agner Fog的表甚至没有显示Nehalem上PCLMULQDQ的uops数量或它们需要哪些端口。它的延迟为12个时钟周期,吞吐量为每8个时钟周期一个,因此它可能类似于Sandy/Ivybridge的18个uop实现。Haswell将其缩减为3个uop(2p0 p5),而在Broadwell(p0)和Skylake(p5)上仅需1个uop即可运行。
XORPS只能在port5上运行(直到Skylake才在所有三个矢量ALU端口上运行)。在Nehalem上,当其中一个输入来自PXOR时,它有2个时钟周期的旁路延迟。在SnB系列CPU上,Agner Fog说:
在某些情况下,使用错误类型的洗牌或布尔指令时没有旁路延迟。
所以我认为在SnB上从PXOR到XORPS的转发实际上没有额外的旁路延迟,因此唯一的影响是它只能在端口5上运行。在Nehalem上,它可能会延迟XORPS直到PSHUFB完成。
在主循环展开中,XOR之后有一个PSHUFB,为下一个PCLMUL设置输入。 SnB/IvB可以在p1/p5上运行整数洗牌(与Haswell及更高版本不同,在p5上仅有一个256位宽度的AVX2洗牌单元)。
由于争夺为下一个PCLMUL设置输入所需的端口似乎没有用处,如果在针对SnB进行调优时进行了此更改,则我的最佳猜测是代码大小/对齐。
在需要超过4个微操作的CPU上,PCLMULQDQ会进行微代码处理。这意味着每个PCLMULQDQ需要一个完整的微操作缓存行。由于只有3个微操作缓存行可以映射到同一32B x86指令块,这意味着大部分代码在SnB/IvB上根本无法适应微操作缓存。每个微操作缓存行只能缓存连续的指令。根据英特尔的优化手册:
Way(微操作缓存行)中的所有微操作表示静态连续的代码指令,并且它们的EIP位于同一对齐的32字节区域内。
这似乎是类似于在循环中使用整数DIV的问题:
在英特尔SnB系列CPU上涉及微代码指令的循环分支对齐。通过正确的对齐,
您可以使其从uop缓存(在英特尔性能计数器术语中为DSB)中运行。@Iwillnotexist Idonotexist在Haswell CPU上进行了一些有用的测试,显示它们防止从loopback缓冲区运行。(在英特尔术语中为LSD)。
在Haswell和之后的处理器上,PCLMULQDQ没有微码,因此可以与其他指令一起放入同一个uop缓存行中。
对于以前的CPU,调整代码以在更少的位置上破坏uop缓存可能是值得尝试的。另一方面,切换uop缓存和传统解码器可能比仅从解码器运行更糟糕。
此外,我不知道这样大的展开是否真的有帮助。这可能在SnB和Skylake之间差异很大,因为微码指令对管道来说非常不同,而SKL甚至可能不会在PCLMUL吞吐量上出现瓶颈。
xorps
是一个三字节指令,而pxor
则需要四个字节。除此之外, Agner Fog 的指令表和微架构手册表明,在 AMD 上没有问题,因为xorps
被视为整数域。但是,这在早期的 Intel 处理器上可能会影响性能,因为在那里,xorps
不能使用太多的执行单元,并且可能存在旁路延迟。 - EOF