混合使用 pxor 和 xorps 会影响性能吗?

Does using mix of pxor and xorps affect performance?

我遇到了 fast CRC computation using PCLMULQDQ implementation。 我明白了,那些人混合了 pxorxorps 指令,就像下面的片段:

movdqa  xmm10, [rk9]
movdqa  xmm8, xmm0
pclmulqdq xmm0, xmm10, 0x11
pclmulqdq xmm8, xmm10, 0x0
pxor  xmm7, xmm8
xorps xmm7, xmm0

movdqa  xmm10, [rk11]
movdqa  xmm8, xmm1
pclmulqdq xmm1, xmm10, 0x11
pclmulqdq xmm8, xmm10, 0x0
pxor  xmm7, xmm8
xorps xmm7, xmm1

这有什么实际原因吗?性能提升?如果是,那么这下面是什么?或者这可能只是一种编码风格,为了好玩?

TL:DR:看起来可能是针对 this 特定代码序列的一些微架构特定调整。在其他情况下,"generally recommended" 没有任何帮助。

进一步考虑,我认为@Iwillnotexist Idonotexist 的理论最有可能:这是由一位认为这可能有帮助的非专家撰写的。寄存器分配是一个很大的线索:通过选择低 8 中所有重复使用的寄存器,可以避免许多 REX 前缀。


XORPS 运行s 在 "float" 域中,在某些 Intel CPUs(Nehalem 和更高版本)上,而 PXOR 总是 运行s 在 "ivec" 域名.

由于将每个 ALU 输出连接到每个 ALU 输入以直接转发结果的成本很高,CPU 设计人员将它们分解为域。 (转发节省了写回寄存器文件和重新读取的延迟)。跨域可能需要额外 1 个周期的延迟(英特尔 SnB 系列)或 2 个周期(Nehalem)。

进一步阅读:我在 What's the difference between logical SSE intrinsics?

上的回答

我想到了两种理论:

  • 写这篇文章的人认为 PXOR 和 XORPS 会提供更多的并行性,因为它们不会相互竞争。 (这是错误的:PXOR 可以 运行 在所有向量 ALU 端口上,但 XORPS 不能)。

  • 这是一些经过巧妙调整的代码,它们有意创建旁路延迟,以避免可能延迟下一个 PCLMULQDQ 执行的资源冲突。 (或者正如 EOF 所建议的那样,代码大小/对齐可能与它有关)。

代码上的版权声明上写着“2011-2015 Intel”,因此值得考虑的是它对某些最近的 Intel CPU 有某种帮助的可能性,而不仅仅是基于对如何使用的误解英特尔 CPU 的工作。 Nehalem 是第一个 CPU 完全包含 PCLMULQDQ 的人,这是英特尔,所以如果有的话,它会被调整为在 AMD CPU 上表现不佳。代码历史不在 git 仓库中,只有 5 月 6 日添加当前版本的提交。

它所基于的 Intel whitepaper (from Dec 2009) 在其 2x pclmul / 2x xor 块的版本中仅使用了 PXOR,而不是 XORPS。

Agner Fog 的 table 甚至没有显示 Nehalem 上 PCLMULQDQ 的微指令数,或者它们需要哪些端口。它是 12c 延迟,每 8c 吞吐量一个,因此它可能类似于 Sandy/Ivybridge 的 18 uop 实现。 Haswell 使其达到令人印象深刻的 3 微指令 (2p0 p5),而它 运行 在 Broadwell (p0) 和 Skylake (p5) 上仅需 1 微指令。

XORPS 只能在端口 5 上 运行(直到 Skylake,它在所有三个向量 ALU 端口上也 运行)。在 Nehalem 上,当其输入之一来自 PXOR 时,旁路延迟为 2c。关于 SnB 家族 CPUs,Agner Fog 说:

In some cases, there is no bypass delay when using the wrong type of shuffle or Boolean instruction.

所以我认为从 SnB 上的 PXOR -> XORPS 转发实际上没有额外的旁路延迟,所以唯一的影响是它只能 运行 在端口 5 上。 On Nehalem , 它实际上可能会延迟 XORPS 直到 PSHUFB 完成之后。

在展开的主循环中,XOR 之后有一个 PSHUFB,用于为下一个 PCLMUL 设置输入。 SnB/IvB 可以 运行 在 p1/p5 上进行整数洗牌(不像 Haswell 和后来的 p5 上只有一个洗牌单元。但它是 256b 宽,对于 AVX2)。

由于竞争为下一个 PCLMUL 设置输入所需的端口似乎没有用,我最好的猜测是代码大小/对齐 如果此更改是在以下时间完成的调整 SnB。


在 PCLMULQDQ 超过 4 微指令的 CPUs 上,它被微编码。这意味着每个 PCLMULQDQ 都需要一个完整的 uop 缓存行。由于只有 3 个 uop 缓存行可以映射到相同的 32B 块 x86 指令,这意味着很多代码根本无法放入 SnB/IvB 上的 uop 缓存。 uop缓存的每一行只能缓存连续的指令。来自 Intel 的优化手册:

All micro-ops in a Way (uop cache line) represent instructions which are statically contiguous in the code and have their EIPs within the same aligned 32-byte region.

这听起来与在循环中使用整数 DIV 非常相似: Branch alignment for loops involving micro-coded instructions on Intel SnB-family CPUs. With the right alignment, you can get it to run out of the uop cache (the DSB in Intel performance counter terminology)。 @Iwillnotexist Idonotexist 对微编码指令的 Haswell CPU 进行了一些有用的测试,表明它们阻止 运行ning 来自环回缓冲区。 (英特尔术语中的 LSD)。


在 Haswell 及更高版本上,PCLMULQDQ 未进行微编码,因此它可以与它之前或之后的其他指令进入同一 uop 缓存行。

对于之前的 CPUs,可能值得尝试调整代码以在更少的地方破坏 uop 缓存。 OTOH,在 uop 缓存和传统解码器之间切换可能比总是从解码器 运行ning 更糟糕。

如果这么大的展开真的很有帮助,我也知道。 SnB 和 Skylake 之间可能差异很大,因为流水线的微编码指令非常不同,而且 SKL 甚至可能不会成为 PCLMUL 吞吐量的瓶颈。