假设您要将rax
和rdx
中的值加载到xmm
寄存器中。
一种方法是:
movq xmm0, rax
pinsrq xmm0, rdx, 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的存储吞吐量很容易成为瓶颈)。
根据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=80820和https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80833,更多地讨论了32位或64位元素的各种体系结构可能是最优的。
我建议上面的vpbroadcastq
替换第一个bug上的AVX512插入。
(如果正在编译_mm_set_epi64x
,请务必使用-mtune=haswell
或更新的内容,以避免对默认mtune=generic
进行粗略调整。或者如果您的二进制文件只运行,请使用-march=native
在本地机器上。)