Ale*_*hak 6 x86 assembly sse simd
我遇到了使用PCLMULQDQ实现的快速CRC计算.我看到,那些人混合pxor
和xorps
说明很像下面的片段:
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
Run Code Online (Sandbox Code Playgroud)
这有什么实际的理由吗?性能提升?如果是,那么这下面是什么?或者它可能只是一种编码风格,有趣吗?
TL:DR:看起来可能是针对这个特定代码序列的一些微特定的调优.关于它的"通常建议"没有什么可以帮助其他情况.
在进一步考虑时,我认为@Iwillnotexist Idonotexist的理论最有可能:这是由一位认为这可能有所帮助的非专家撰写的.寄存器分配是一个很大的线索:通过选择低8中所有重复使用的寄存器,可以避免许多REX前缀.
XORPS在"浮动"域中运行,在某些Intel CPU(Nehalem和更高版本)上运行,而PXOR始终在"ivec"域中运行.
由于将每个ALU输出连接到每个ALU输入以直接转发结果将是昂贵的,因此CPU设计者将它们分解为域.(转发可以节省写回寄存器文件和重新读取的延迟).域跨越可能需要额外的1个周期的延迟(Intel SnB系列)或2个周期(Nehalem).
进一步阅读:我的回答是什么逻辑SSE内在函数之间有什么区别?
有两种理论出现在我面前:
谁写了这个想法,PXOR和XORPS会提供更多的并行性,因为它们不会相互竞争.(这是错误的:PXOR可以在所有矢量ALU端口上运行,但XORPS不能).
这是一些非常巧妙调整的代码,可以故意创建旁路延迟,以避免可能延迟执行下一个PCLMULQDQ的资源冲突.(或者如EOF所示,代码大小/对齐可能与它有关).
该代码的版权声明称"2011-2015英特尔",因此值得考虑它对某些最近的英特尔CPU有所帮助的可能性,而不仅仅是基于对英特尔CPU如何工作的误解.Nehalem是第一个包含PCLMULQDQ的CPU,这就是英特尔,所以如果有任何东西它将被调整为在AMD CPU上做得很糟糕.代码历史记录不在git repo中,只是添加了当前版本的5月6日提交.
在英特尔白皮书(从2009年12月),它是基于使用PXOR只,而不是XORPS在其版本的2倍pclmul/2X XOR块.
Agner Fog的表格甚至没有显示Nehalem上PCLMULQDQ的数量,或者他们需要哪些端口.这是12c延迟,每8c吞吐量一个,所以它可能类似于Sandy/Ivybridge的18 uop实现.Haswell令人印象深刻的3 uops(2p0 p5),而在Broadwell(p0)和Skylake(p5)上只有1 uop.
XORPS只能在port5上运行(直到Skylake,它也可以在所有三个矢量ALU端口上运行).当其中一个输入来自PXOR时,Nehalem有2c旁路延迟.在SnB系列CPU上,Agner Fog说:
在某些情况下,使用错误类型的shuffle或布尔指令时没有旁路延迟.
因此我认为实际上没有额外的旁路延迟来从PXOR转发 - > Snor上的XORPS,因此唯一的影响是它只能在端口5 上运行. 在Nehalem上,它实际上可能会延迟XORPS直到PSHUFB完成之后.
在主展开循环中,在XOR之后有一个PSHUFB,用于设置下一个PCLMUL的输入.SnB/IvB可以在p1/p5上运行整数shuffle(与Haswell不同,之后在p5上只有一个shuffle单元.但对于AVX2,它的宽度为256b).
由于竞争为下一个PCLMUL设置输入所需的端口似乎没有用,我最好的猜测是代码大小/对齐,如果在调整SnB时完成此更改.
在PCLMULQDQ超过4 uops的CPU上,它是微编码的.这意味着每个PCLMULQDQ都需要一个完整的uop缓存行.由于只有3个uop缓存行可以映射到x86指令的相同32B块,这意味着很多代码在SnB/IvB上根本不适合uop缓存.uop缓存的每一行只能缓存连续的指令.来自英特尔的优化手册:
一个方式中的所有微操作(uop缓存行)表示在代码中静态连续的指令,并且它们的EIP在相同的对齐的32字节区域内.
这听起来像是在循环中使用整数DIV非常相似的问题:在Intel SnB系列CPU上涉及微编码指令的循环的 分支对齐.通过正确的对齐,您可以使用uop缓存(英特尔性能计数器术语中的DSB).@Iwillnotexist Idonotexist对Haswell CPU的微编码指令进行了一些有用的测试,表明它们阻止了从环回缓冲区运行.(英特尔术语中的LSD).
在Haswell及更高版本中,PCLMULQDQ不是微编码的,因此它可以在其之前或之后与其他指令一起进入相同的uop缓存行.
对于以前的CPU,可能值得尝试调整代码以在较少的位置破坏uop缓存.OTOH,在uop缓存和传统解码器之间切换可能比总是从解码器运行更糟糕.
IDK如果这么大的展开真的很有用.它可能在SnB和Skylake之间变化很大,因为管道的微编码指令非常不同,SKL甚至可能不会影响PCLMUL的吞吐量.