从GP regs加载xmm

时间:2018-06-09 23:15:58

标签: assembly x86 sse simd micro-optimization

假设您要将raxrdx中的值加载到xmm寄存器中。

一种方法是:

movq     xmm0, rax
pinsrq   xmm0, rdx, 1

虽然这很慢!还有更好的方法吗?

1 个答案:

答案 0 :(得分:6)

你对最近的英特尔或AMD的延迟或uop计数不会做得更好(我主要关注Ryzen / Skylake的Agner Fog表)。对于相同的端口,movq+movq+punpcklqdq也是3 uops。

在Intel / AMD上,将GP寄存器存储到临时位置并使用16字节读取重新加载它们可能值得考虑吞吐量,如果ALU端口上的代码瓶颈存在整数 - >向量,即端口5对于最近的英特尔。

在英特尔上,端口5的pinsrq x,r,imm为2 uop,端口5的movq xmm,r64也为1 uop。

movhps xmm, [mem]可以微量融合负载,但它仍然需要一个端口5 ALU uop。因此,movq xmm0,rax / mov [rsp-8], rdx / movhps xmm0, [rsp-8]是3个融合域uops,其中2个在最近的Intel上需要端口5。存储转发延迟使得延迟明显高于插入。

使用存储/存储/ movdqa存储两个GP regs(读取具有较大负载的两个较窄存储的长存储转发停顿)也是3 uops,但是唯一合理的序列避免任何端口5微指令。大约15个延迟周期非常多,以至于无序执行很容易将其隐藏起来。

对于YMM和/或更窄的元素,商店+重新加载更值得考虑,因为您可以在更多商店中分摊摊位/它可以节省您更多的随机微博。但它仍然不应该成为32位元素的首选策略。

对于较窄的元素,如果有一个单一的方法将2个窄整数打包到64位整数寄存器中会很好,所以设置为更宽的传输到XMM regs。但是没有:Packing two DWORDs into a QWORD to save store bandwidth shld在Intel SnB系列上是1 uop但需要寄存器顶部的一个输入。与PowerPC或ARM相比,x86具有非常弱的位域插入/提取指令,每次合并需要多个指令(除了存储/重新加载,每个时钟1的存储吞吐量很容易成为瓶颈)。

AVX512F可以broadcast to a vector from an integer reg,并且合并屏蔽允许单uop插入。

根据http://instlatx64.atw.hu/的电子表格(从IACA获取uop数据),在Skylake-AVX512上将任意宽度的整数寄存器广播到x / y / zmm向量只需要1 port5 uop。

Agner似乎没有在KNL上测试整数源寄存器,但类似的VPBROADCASTMB2Q v,k(掩码寄存器源)是1 uop。

已设置掩码寄存器:总共只有2个

; k1 = 0b0010

vmovq         xmm0, rax           ; 1 uop p5             ; AVX1
vpbroadcastq  xmm0{k1}, rdx       ; 1 uop p5  merge-masking

认为合并掩盖是"免费"即使是ALU uops。请注意,我们首先执行VMOVQ,以便我们可以避免使用更长的EVEX编码。但是,如果您在掩码reg而不是0001中使用0010,请将其与vmovq xmm0{k1}, rax的无掩码广播混合。

设置了更多掩码寄存器后,我们可以为每个uop执行1个寄存器:

vmovq         xmm0, rax                         2c latency
vpbroadcastq  xmm0{k1}, rdx   ; k1 = 0b0010     3c latency
vpbroadcastq  ymm0{k2}, rdi   ; k2 = 0b0100     3c latency
vpbroadcastq  ymm0{k3}, rsi   ; k3 = 0b1000     3c latency

(对于一个完整的ZMM向量,可能会启动第二个dep链和vinserti64x4组合256位一半。也意味着只有3 k个寄存器而不是7个。它需要1个额外的shuffle uop,但除非有&# 39;一些软件流水线操作,OoO exec在您对向量执行任何操作之前可能无法隐藏7个merges = 21c的延迟。)

; high 256 bits: maybe better to start again with vmovq instead of continuing
vpbroadcastq  zmm0{k4}, rcx   ; k4 =0b10000     3c latency
... filling up the ZMM reg

英特尔在SKX上列出的vpbroadcastq延迟仍然是3c,即使目标只有xmm,根据引用该数据和其他来源的Instlatx64电子表格。 http://instlatx64.atw.hu/

同一文档将vpbroadcastq xmm,xmm列为1c延迟,因此可能正确的是我们在合并依赖关系链中每步获得3c延迟。遗憾的是,合并屏蔽uops需要目标寄存器尽早准备好其他输入;因此,操作的合并部分不能单独转发。

k1 = 2 = 0b0010开始,我们可以使用KSHIFT初始化其余内容:

mov      eax, 0b0010 = 2
kmovw    k1, eax
KSHIFTLW k2, k1, 1
KSHIFTLW k3, k1, 2

#  KSHIFTLW k4, k1, 3
# ...

KSHIFT仅在端口5(SKX)上运行,但KMOV也是如此;从整数寄存器移动每个掩码只需花费额外的指令来设置整数寄存器。

如果向量的高位字节用广播而不是零填充,那么它确实没问题,所以我们可以使用0b1110 / 0b1100等掩码。
 我们最终写出所有元素。我们可以从KXNOR k0, k0,k0开始生成-1和左移,但是那个2 port5 uops与mov eax,2 / kmovw k1, eax是p0156 + p5。

没有掩码寄存器 :(没有kmov k1, imm,从内存加载需要多次uops,所以一次性没有3-使用merge-masking的uop选项。但是如果你可以节省一些掩码regs,那么这似乎更好。)

VPBROADCASTQ  xmm1, rdx           ; 1 uop  p5      ; AVX512VL (ZMM1 for just AVX512F)
vmovq         xmm0, rax           ; 1 uop p5             ; AVX1
vpblendd      xmm0, xmm0, xmm1, 0b1100    ; 1 uop p015   ; AVX2

; SKX: 3 uops:  2p5 + p015
; KNL: 3 uops: ? + ? + FP0/1

这里唯一的好处是3个uops中的一个不需要端口5.

vmovsd xmm1, xmm1, xmm0也会混合两半,但只在最近的英特尔上运行在端口5上,而不像在任何矢量ALU端口上运行的整数立即混合。

关于整数的更多讨论 - >矢量策略

gcc喜欢存储/重新加载,这对于任何事情都不是最佳的,除非是非常罕见的端口5绑定情况,其中大量的延迟并不重要。我提交了https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80820https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80833,更多地讨论了32位或64位元素的各种体系结构可能是最优的。

我建议上面的vpbroadcastq替换第一个bug上的AVX512插入。

(如果正在编译_mm_set_epi64x,请务必使用-mtune=haswell或更新的内容,以避免对默认mtune=generic进行粗略调整。或者如果您的二进制文件只运行,请使用-march=native在本地机器上。)