从 GP 注册加载 xmm



假设您在rax中具有值,并且rdx要加载到xmm寄存器中。

一种方法是:

movq     xmm0, rax
pinsrq   xmm0, rdx, 1

不过很慢!有没有更好的方法?

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

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

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

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

存储两个具有存储/存储/movdqa的 GP reg(读取具有较大负载的两个较窄存储的长存储转发停顿)也是 3 uops,但这是唯一避免任何端口 5 uops 的合理序列。 ~15 个周期的延迟是如此之多,以至于无序执行很容易隐藏它。


对于 YMM 和/或更窄的元素,商店 + 重新加载更值得考虑,因为您将摊位摊销到更多商店/它为您节省了更多的随机 uops。 但它仍然不应该成为 32 位元素的首选策略。

对于较窄的元素,如果有一种单 uop 方法将 2 个窄整数打包到 64 位整数寄存器中,那就太好了,因此请设置为更广泛地传输到 XMM regs。 但事实并非如此:将两个 DWORD 打包到一个 QWORD 中以节省存储带宽shld在英特尔 SnB 家族上为 1 uop,但需要寄存器顶部的输入之一。 与PowerPC或ARM相比,x86的位域插入/提取指令非常弱,每次合并需要多个指令(除了存储/重新加载,每个时钟1个存储吞吐量很容易成为瓶颈)。


AVX512F可以从整数 reg 广播到向量,合并掩码允许单 uop 插入。

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

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

已经设置了掩码寄存器:总共只有 2 uops

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

我认为合并掩码即使对于 ALU uops 也是"免费"的。 请注意,我们首先执行 VMOVQ,以便避免对其进行更长的 EVEX 编码。 但是,如果您有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 个随机 uop,但除非有一些软件流水线,否则 OoO exec 在对向量执行任何操作之前可能无法隐藏 7 次合并 = 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 端口 5 uops 与mov eax,2/kmovw k1, eax为 p0156 + p5。

没有

掩码寄存器:(没有kmov k1, imm,从内存加载需要花费多个uops,因此作为一次性,没有使用合并掩码的3-uop选项。 但是在一个循环中,如果你能省去一些面具注册,那似乎要好得多

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 位元素的各种体系结构上什么是最佳选择。

我建议在第一个错误中用AVX512替换上述vpbroadcastq插入。

(如果编译_mm_set_epi64x,一定要使用-mtune=haswell或最近的东西,以避免默认mtune=generic的蹩脚调整。 或者,如果二进制文件仅在本地计算机上运行,请使用-march=native

最新更新