assembly - 从GP规则加载xmm

标签 assembly x86 sse simd micro-optimization

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

一种方法是:

movq     xmm0, rax
pinsrq   xmm0, rdx, 1


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

最佳答案

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

在Intel / AMD上,将GP寄存器存储到一个临时位置并用16字节的读取值重新加载它们可能是值得考虑的吞吐量,前提是ALU端口上的整数瓶颈->向量(最近Intel的端口5)周围的代码瓶颈。

在Intel上,端口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。存储转发延迟使此延迟比插入延迟高得多。

将两个GP寄存器与store / store / movdqa存储在一起(从读取较大负载的两个较窄存储中读取长存储转发档)也为3 ups,但这是避免任何端口5 uop的唯一合理顺序。大约15个周期的延迟如此之多,以至于乱序执行很容易将其隐藏起来。



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

对于较窄的元素,如果有将2个较窄的整数打包到64位整数寄存器中的单-uop方式,那就很好了,因此可以进行更广泛的XMM寄存器传输。但是没有: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,并且合并掩码允许单uup插入。

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

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微指令,合并屏蔽也是“免费的”。请注意,我们首先要做VMOVQ,因此可以避免使用更长的EVEX编码。但是,如果您在掩码reg中使用0001而不是0010,请使用vmovq xmm0{k1}, rax将其混合到未掩码的广播中。

通过设置更多的掩码寄存器,我们可以为每个uop做1个reg:

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,但是除非有一些软件流水线操作,否则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


根据Instlatx64电子表格(引用该来源和其他来源)的报道,即使目的地仅为xmm,英特尔在SKX上列出的vpbroadcastq延迟仍为3c。 http://instlatx64.atw.hu/

同一份文档确实将vpbroadcastq xmm,xmm列为1c延迟,因此可以肯定的是,合并依赖链中的每一步我们都获得3c延迟。不幸的是,合并屏蔽的微指令需要目标寄存器早于其他输入就准备就绪。因此该操作的合并部分无法单独转发。



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也是如此。将每个掩码从整数寄存器中移出只会花费额外的指令来首先设置整数reg。

如果向量的高字节用广播而不是零填充,实际上是可以的,因此我们可以使用0b1110 / 0b1100等作为掩码。
我们最终写出所有元素。我们可以从KXNOR k0, k0,k0开始生成-1并向左移,但这是2个port5 uops,而mov eax,2 / kmovw k1, eax为p0156 + p5。



没有掩码寄存器:(没有kmov k1, imm,并且从内存中加载需要花费多倍,因此,一次性使用合并掩码就没有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也可以将这两个部分混合在一起,但只能在最近的Intel的端口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位元素的各种体系结构上最佳选择。

我建议在第一个bug上使用AVX512进行上述vpbroadcastq替换。

(如果编译_mm_set_epi64x,请绝对使用-mtune=haswell或最近的版本,以避免对默认的mtune=generic进行繁琐的调整。或者,如果二进制文件仅在本地计算机上运行,​​则使用-march=native。)

关于assembly - 从GP规则加载xmm,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50779309/

相关文章:

c++ - SSE4.1 自动在较新的 gcc 上进行字符串比较

math - TMS320C55X 中的反正切实现

Python程序变成标准程序集?

assembly - 是否可以移植 .8xk 应用程序以适用于 TI 84 Plus CE?

assembly - xchg 如何在英特尔汇编语言中工作

c++ - 性能 AVX/SSE 汇编与内部函数

assembly - 我们看到一个进程的虚拟地址(在分页系统中),这些虚拟地址存在哪里?

assembly - FMUL 不会清除 STATUS 寄存器中的溢出

caching - Linux 是否将 x86 CPU 的 PCID 功能用于 TLB?如果不是,为什么?

c++ - SSE 类型的 pow