我的代码中有大量的punpckl,pextrd和pinsrd,它们旋转8x8字节矩阵,作为旋转带有循环的黑白图像的较大例程的一部分。
我用IACA对其进行了分析,看看是否值得为AVX2程序做好准备,而且令人惊讶的是,Haswell / Skylake的代码几乎是IVB的两倍(IVB:19.8,HSW,SKL:36个周期)。 (IVB + HSW使用iaca 2.1,skl使用3.0,但hsw给出相同的数字3.0)
根据IACA输出,我猜不同之处在于IVB使用端口1和5作为上述指令,而haswell仅使用端口5。
我用Google搜索了一下,但无法找到解释。使用传统的SSE,是否真的慢了,或者我刚刚遇到了一些极端的角落?任何建议躲避这个子弹(AVX2除外,这是一个已知的选项,但由于更新工具链到现在推迟的新版本)欢迎提供一般性评论或建议改进。
// r8 and r9 are #bytes to go to the next line in resp. src and dest
// r12=3*r8 r13=3*r9
// load 8x8 bytes into 4 registers, bytes interleaved.
movq xmm1,[rcx]
movq xmm4,[rcx+2*r8]
PUNPCKLBW xmm1,xmm4 // 0 2 0 2 0 2
movq xmm7,[rcx+r8]
movq xmm6,[rcx+r12]
PUNPCKLBW xmm7,xmm6 // 1 3 1 3 1 3
movdqa xmm2,xmm1
punpcklbw xmm1,xmm7 // 0 1 2 3 0 1 2 3 in xmm1:xmm2
punpckhbw xmm2,xmm7
lea rcx,[rcx+4*r8]
// same for 4..7
movq xmm3,[rcx]
movq xmm5,[rcx+2*r8]
PUNPCKLBW xmm3,xmm5
movq xmm7,[rcx+r8]
movq xmm8,[rcx+r12]
PUNPCKLBW xmm7,xmm8
movdqa xmm4,xmm3
punpcklbw xmm3,xmm7
punpckhbw xmm4,xmm7
// now we join one "low" dword from XMM1:xmm2 with one "high" dword
// from XMM3:xmm4
movdqa xmm5,xmm1
pextrd eax,xmm3,0
pinsrd xmm5,eax,1
movq [rdx],xmm5
movdqa xmm5,xmm3
pextrd eax,xmm1,1
pinsrd xmm5,eax,0
movq [rdx+r9],xmm5
movdqa xmm5,xmm1
pextrd eax,xmm3,2
pinsrd xmm5,eax,3
MOVHLPS xmm6,xmm5
movq [rdx+2*r9],xmm6
movdqa xmm5,xmm3
pextrd eax,xmm1,3
pinsrd xmm5,eax,2
MOVHLPS xmm6,xmm5
movq [rdx+r13],xmm6
lea rdx,[rdx+4*r9]
movdqa xmm5,xmm2
pextrd eax,xmm4,0
pinsrd xmm5,eax,1
movq [rdx],xmm5
movdqa xmm5,xmm4
pextrd eax,xmm2,1
pinsrd xmm5,eax,0
movq [rdx+r9],xmm5
movdqa xmm5,xmm2
pextrd eax,xmm4,2
pinsrd xmm5,eax,3
MOVHLPS xmm6,xmm5
movq [rdx+2*r9],xmm6
movdqa xmm5,xmm4
pextrd eax,xmm2,3
pinsrd xmm5,eax,2
MOVHLPS xmm6,xmm5
movq [rdx+r13],xmm6
lea rdx,[rdx+4*r9]
目的: 它实际上是来自相机的旋转图像,用于图像视觉目的。在一些(较重的)应用程序中,旋转被推迟并完成仅显示(opengl),在某些情况下,更容易旋转输入然后调整算法。
更新代码:我发布了一些最终代码here加速非常依赖于输入的大小。较大的小图像,但与使用32x32图块循环HLL代码相比,在较大的图像上仍然是两倍。 (与asm代码相关的算法相同)
答案 0 :(得分:3)
如果不是通过pshufd和立即混合的组合最有效地完成插入dword,则更多。
pshufd xmm5, xmm3, 0x55 * slot
pblendw xmm1, xmm5, 3 << dst_slot
pblendw is SSE4.1,但当然可以在haswell上找到。不幸的是,它只能在Haswell / Skylake的5号端口运行,因此它仍然可以与shuffle竞争。
AVX2 vpblendd
在Haswell / Skylake上的任何vector-ALU端口(p0 / p1 / p5)上运行,因此比word-granularity pblendw
/ vpblendw
更有效。< / p>
如果您需要避免AVX2,请考虑使用SSE4.1 blendps
将32位元素与立即控制混合。它可以在Haswell上的任何端口上运行(或者在Sandybridge上使用p0 / p5,对于shuffle使用p1 / p5),并且在整数数据上使用它的延迟惩罚应该与您的情况无关。
答案 1 :(得分:3)
TL:DR:在dword-rearranging步骤中使用punpckl/hdq
来保存大量的随机播放,就像A better 8x8 bytes matrix transpose with SSE?中的转置代码一样
您的内存布局需要单独存储每个矢量结果的低/高8字节,您可以使用movq [rdx], xmm
/ movhps [rdx+r9], xmm
高效地执行此操作。
Haswell / Skylake的代码几乎是IVB的两倍
您的代码严重影响了随机播放吞吐量。
Haswell在端口5上只有一个shuffle执行单元.SnB / IvB有2个整数shuffle单元(但仍然只有一个FP shuffle单元)。请参阅Agner Fog's instruction tables and optimization guide / microarch guide。
我看到你已经找到David Kanter出色的Haswell microarch write-up。
对于像这样的代码,很容易出现乱码(或一般来说是port5)吞吐量的瓶颈,并且AVX / AVX2经常会变得更糟,因为很多shuffle只是在通道内。用于128位操作的AVX可能有所帮助,但我认为你不会从改组到256b向量中获得任何东西,然后将它们再次分成64位块。如果你可以加载或存储连续的256b块,那么值得尝试。在我们考虑重大变化之前,你有一些简单的遗漏优化:
MOVHLPS xmm6,xmm5
movq [rdx+r13],xmm6
应为movhps [rdx+r13],xmm6
。在Sandybridge和Haswell,movhps
是一个纯粹的商店uop,不需要随机播放。
pextrd eax,xmm3,0
总是比movd eax, xmm3
更糟糕;永远不要使用pextrd
立即使用0.(另外,pextrd
直接对内存可能是一个胜利。你可以做一个64位movq
,然后用32-覆盖其中一半位pextrd
。然后你可能会对商店吞吐量产生瓶颈。另外,在Sandybridge,indexed addressing modes don't stay micro-fused,所以更多的商店会损害你的总uop吞吐量。但Haswell没有商店的问题,只有一些索引加载取决于指令。)如果你在某些地方使用更多的存储,而在其他地方使用更多的存储,你可以使用更多的存储来进行单寄存器寻址模式。
源和目标格式不是图像处理的自由度。
取决于你在做什么。 x264(开源h.264视频编码器)将8x8块复制到连续的缓冲区中,然后重复使用它们,因此行之间的步幅是汇编时常量。
这样可以节省通过寄存器的步伐,并执行与[rcx+2*r8]
/ [rcx+r8]
相关的操作。它还允许您使用一个movdqa
加载两行。它为您提供了访问8x8块的良好内存位置。
当然,如果这个旋转是所有你正在使用8x8像素块,那么花时间复制这种格式可能并不是一件好事。 FFmpeg的h.264解码器(它使用许多与x264相同的asm原语)不使用它,但IDK是因为没有人不愿意移植更新的x264 asm,或者它只是不值得。
// now we join one "low" dword from XMM1:xmm2 with one "high" dword // from XMM3:xmm4
从整数中提取/插入效率不高; pinsrd
和pextrd
各有2个uop,其中一个uops是shuffle。您甚至可以使用pextrd
以32位块的形式使用当前代码。
还可以考虑使用SSSE3 pshufb
,它可以将您的数据放在需要的任何位置,并将其他元素归零。这可以设置为与por
合并。 (您可以使用pshufb
代替punpcklbw
)。
另一种选择是使用shufps
来合并来自两个来源的数据。之后你可能需要另外一次洗牌。 或使用punpckldq
。
// "low" dwords from XMM1:xmm2
// high dwords from XMM3:xmm4
; xmm1: [ a b c d ] xmm2: [ e f g h ]
; xmm3: [ i j k l ] xmm4: [ m n o p ]
; want: [ a i b j ] / [ c k d l ] / ... I think.
;; original: replace these with
; movdqa xmm5,xmm1 ; xmm5 = [ a b c d ]
; pextrd eax,xmm3,0 ; eax = i
; pinsrd xmm5,eax,1 ; xmm5 = [ a i ... ]
; movq [rdx],xmm5
; movdqa xmm5,xmm3 ; xmm5 = [ i j k l ]
; pextrd eax,xmm1,1 ; eax = b
; pinsrd xmm5,eax,0 ; xmm5 = [ b j ... ]
; movq [rdx+r9],xmm5
替换为:
movdqa xmm5, xmm1
punpckldq xmm5, xmm3 ; xmm5 = [ a i b j ]
movq [rdx], xmm5
movhps [rdx+r9], xmm5 ; still a pure store, doesn't cost a shuffle
所以我们用1替换了4个shuffle uop,并将总uop数量从12个融合域uops(Haswell)降低到4.(或者在Sandybridge上,从13到5,因为索引存储不能保持微观 - 融合)。
将punpckhdq
用于[ c k d l ]
,它会更好,因为我们也会替换movhlps
。
; movdqa xmm5,xmm1 ; xmm5 = [ a b c d ]
; pextrd eax,xmm3,2 ; eax = k
; pinsrd xmm5,eax,3 ; xmm5 = [ a b c k ]
; MOVHLPS xmm6,xmm5 ; xmm6 = [ c k ? ? ] (false dependency on old xmm6)
; movq [rdx+2*r9],xmm6
然后解压缩lo / hi为xmm2和xmm4。
使用AVX或AVX2可以跳过movdqa
,因为您可以解压缩到新的目标寄存器而不是复制+销毁。